fix for bug #27666: naming of variants
[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 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void ics_printf P((char *format, ...));
151 void SendToICS P((char *s));
152 void SendToICSDelayed P((char *s, long msdelay));
153 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154                       int toX, int toY));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162                   Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166                    /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177                            char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179                         int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     if (appData.matchGames > 0) {
1046         appData.matchMode = TRUE;
1047     } else if (appData.matchMode) {
1048         appData.matchGames = 1;
1049     }
1050     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051         appData.matchGames = appData.sameColorGames;
1052     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1055     }
1056     Reset(TRUE, FALSE);
1057     if (appData.noChessProgram || first.protocolVersion == 1) {
1058       InitBackEnd3();
1059     } else {
1060       /* kludge: allow timeout for initial "feature" commands */
1061       FreezeUI();
1062       DisplayMessage("", _("Starting chess program"));
1063       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1064     }
1065 }
1066
1067 void
1068 InitBackEnd3 P((void))
1069 {
1070     GameMode initialMode;
1071     char buf[MSG_SIZ];
1072     int err;
1073
1074     InitChessProgram(&first, startedFromSetupPosition);
1075
1076
1077     if (appData.icsActive) {
1078 #ifdef WIN32
1079         /* [DM] Make a console window if needed [HGM] merged ifs */
1080         ConsoleCreate(); 
1081 #endif
1082         err = establish();
1083         if (err != 0) {
1084             if (*appData.icsCommPort != NULLCHAR) {
1085                 sprintf(buf, _("Could not open comm port %s"),  
1086                         appData.icsCommPort);
1087             } else {
1088                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1089                         appData.icsHost, appData.icsPort);
1090             }
1091             DisplayFatalError(buf, err, 1);
1092             return;
1093         }
1094         SetICSMode();
1095         telnetISR =
1096           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1097         fromUserISR =
1098           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099     } else if (appData.noChessProgram) {
1100         SetNCPMode();
1101     } else {
1102         SetGNUMode();
1103     }
1104
1105     if (*appData.cmailGameName != NULLCHAR) {
1106         SetCmailMode();
1107         OpenLoopback(&cmailPR);
1108         cmailISR =
1109           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1110     }
1111     
1112     ThawUI();
1113     DisplayMessage("", "");
1114     if (StrCaseCmp(appData.initialMode, "") == 0) {
1115       initialMode = BeginningOfGame;
1116     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117       initialMode = TwoMachinesPlay;
1118     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119       initialMode = AnalyzeFile; 
1120     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121       initialMode = AnalyzeMode;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123       initialMode = MachinePlaysWhite;
1124     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125       initialMode = MachinePlaysBlack;
1126     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127       initialMode = EditGame;
1128     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129       initialMode = EditPosition;
1130     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131       initialMode = Training;
1132     } else {
1133       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134       DisplayFatalError(buf, 0, 2);
1135       return;
1136     }
1137
1138     if (appData.matchMode) {
1139         /* Set up machine vs. machine match */
1140         if (appData.noChessProgram) {
1141             DisplayFatalError(_("Can't have a match with no chess programs"),
1142                               0, 2);
1143             return;
1144         }
1145         matchMode = TRUE;
1146         matchGame = 1;
1147         if (*appData.loadGameFile != NULLCHAR) {
1148             int index = appData.loadGameIndex; // [HGM] autoinc
1149             if(index<0) lastIndex = index = 1;
1150             if (!LoadGameFromFile(appData.loadGameFile,
1151                                   index,
1152                                   appData.loadGameFile, FALSE)) {
1153                 DisplayFatalError(_("Bad game file"), 0, 1);
1154                 return;
1155             }
1156         } else if (*appData.loadPositionFile != NULLCHAR) {
1157             int index = appData.loadPositionIndex; // [HGM] autoinc
1158             if(index<0) lastIndex = index = 1;
1159             if (!LoadPositionFromFile(appData.loadPositionFile,
1160                                       index,
1161                                       appData.loadPositionFile)) {
1162                 DisplayFatalError(_("Bad position file"), 0, 1);
1163                 return;
1164             }
1165         }
1166         TwoMachinesEvent();
1167     } else if (*appData.cmailGameName != NULLCHAR) {
1168         /* Set up cmail mode */
1169         ReloadCmailMsgEvent(TRUE);
1170     } else {
1171         /* Set up other modes */
1172         if (initialMode == AnalyzeFile) {
1173           if (*appData.loadGameFile == NULLCHAR) {
1174             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1175             return;
1176           }
1177         }
1178         if (*appData.loadGameFile != NULLCHAR) {
1179             (void) LoadGameFromFile(appData.loadGameFile,
1180                                     appData.loadGameIndex,
1181                                     appData.loadGameFile, TRUE);
1182         } else if (*appData.loadPositionFile != NULLCHAR) {
1183             (void) LoadPositionFromFile(appData.loadPositionFile,
1184                                         appData.loadPositionIndex,
1185                                         appData.loadPositionFile);
1186             /* [HGM] try to make self-starting even after FEN load */
1187             /* to allow automatic setup of fairy variants with wtm */
1188             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189                 gameMode = BeginningOfGame;
1190                 setboardSpoiledMachineBlack = 1;
1191             }
1192             /* [HGM] loadPos: make that every new game uses the setup */
1193             /* from file as long as we do not switch variant          */
1194             if(!blackPlaysFirst) { int i;
1195                 startedFromPositionFile = TRUE;
1196                 CopyBoard(filePosition, boards[0]);
1197                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1198             }
1199         }
1200         if (initialMode == AnalyzeMode) {
1201           if (appData.noChessProgram) {
1202             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1203             return;
1204           }
1205           if (appData.icsActive) {
1206             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1207             return;
1208           }
1209           AnalyzeModeEvent();
1210         } else if (initialMode == AnalyzeFile) {
1211           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212           ShowThinkingEvent();
1213           AnalyzeFileEvent();
1214           AnalysisPeriodicEvent(1);
1215         } else if (initialMode == MachinePlaysWhite) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1218                               0, 2);
1219             return;
1220           }
1221           if (appData.icsActive) {
1222             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1223                               0, 2);
1224             return;
1225           }
1226           MachineWhiteEvent();
1227         } else if (initialMode == MachinePlaysBlack) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineBlackEvent();
1239         } else if (initialMode == TwoMachinesPlay) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           TwoMachinesEvent();
1251         } else if (initialMode == EditGame) {
1252           EditGameEvent();
1253         } else if (initialMode == EditPosition) {
1254           EditPositionEvent();
1255         } else if (initialMode == Training) {
1256           if (*appData.loadGameFile == NULLCHAR) {
1257             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1258             return;
1259           }
1260           TrainingEvent();
1261         }
1262     }
1263 }
1264
1265 /*
1266  * Establish will establish a contact to a remote host.port.
1267  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268  *  used to talk to the host.
1269  * Returns 0 if okay, error code if not.
1270  */
1271 int
1272 establish()
1273 {
1274     char buf[MSG_SIZ];
1275
1276     if (*appData.icsCommPort != NULLCHAR) {
1277         /* Talk to the host through a serial comm port */
1278         return OpenCommPort(appData.icsCommPort, &icsPR);
1279
1280     } else if (*appData.gateway != NULLCHAR) {
1281         if (*appData.remoteShell == NULLCHAR) {
1282             /* Use the rcmd protocol to run telnet program on a gateway host */
1283             snprintf(buf, sizeof(buf), "%s %s %s",
1284                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1285             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1286
1287         } else {
1288             /* Use the rsh program to run telnet program on a gateway host */
1289             if (*appData.remoteUser == NULLCHAR) {
1290                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291                         appData.gateway, appData.telnetProgram,
1292                         appData.icsHost, appData.icsPort);
1293             } else {
1294                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295                         appData.remoteShell, appData.gateway, 
1296                         appData.remoteUser, appData.telnetProgram,
1297                         appData.icsHost, appData.icsPort);
1298             }
1299             return StartChildProcess(buf, "", &icsPR);
1300
1301         }
1302     } else if (appData.useTelnet) {
1303         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1304
1305     } else {
1306         /* TCP socket interface differs somewhat between
1307            Unix and NT; handle details in the front end.
1308            */
1309         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1310     }
1311 }
1312
1313 void
1314 show_bytes(fp, buf, count)
1315      FILE *fp;
1316      char *buf;
1317      int count;
1318 {
1319     while (count--) {
1320         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321             fprintf(fp, "\\%03o", *buf & 0xff);
1322         } else {
1323             putc(*buf, fp);
1324         }
1325         buf++;
1326     }
1327     fflush(fp);
1328 }
1329
1330 /* Returns an errno value */
1331 int
1332 OutputMaybeTelnet(pr, message, count, outError)
1333      ProcRef pr;
1334      char *message;
1335      int count;
1336      int *outError;
1337 {
1338     char buf[8192], *p, *q, *buflim;
1339     int left, newcount, outcount;
1340
1341     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342         *appData.gateway != NULLCHAR) {
1343         if (appData.debugMode) {
1344             fprintf(debugFP, ">ICS: ");
1345             show_bytes(debugFP, message, count);
1346             fprintf(debugFP, "\n");
1347         }
1348         return OutputToProcess(pr, message, count, outError);
1349     }
1350
1351     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1352     p = message;
1353     q = buf;
1354     left = count;
1355     newcount = 0;
1356     while (left) {
1357         if (q >= buflim) {
1358             if (appData.debugMode) {
1359                 fprintf(debugFP, ">ICS: ");
1360                 show_bytes(debugFP, buf, newcount);
1361                 fprintf(debugFP, "\n");
1362             }
1363             outcount = OutputToProcess(pr, buf, newcount, outError);
1364             if (outcount < newcount) return -1; /* to be sure */
1365             q = buf;
1366             newcount = 0;
1367         }
1368         if (*p == '\n') {
1369             *q++ = '\r';
1370             newcount++;
1371         } else if (((unsigned char) *p) == TN_IAC) {
1372             *q++ = (char) TN_IAC;
1373             newcount ++;
1374         }
1375         *q++ = *p++;
1376         newcount++;
1377         left--;
1378     }
1379     if (appData.debugMode) {
1380         fprintf(debugFP, ">ICS: ");
1381         show_bytes(debugFP, buf, newcount);
1382         fprintf(debugFP, "\n");
1383     }
1384     outcount = OutputToProcess(pr, buf, newcount, outError);
1385     if (outcount < newcount) return -1; /* to be sure */
1386     return count;
1387 }
1388
1389 void
1390 read_from_player(isr, closure, message, count, error)
1391      InputSourceRef isr;
1392      VOIDSTAR closure;
1393      char *message;
1394      int count;
1395      int error;
1396 {
1397     int outError, outCount;
1398     static int gotEof = 0;
1399
1400     /* Pass data read from player on to ICS */
1401     if (count > 0) {
1402         gotEof = 0;
1403         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404         if (outCount < count) {
1405             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1406         }
1407     } else if (count < 0) {
1408         RemoveInputSource(isr);
1409         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410     } else if (gotEof++ > 0) {
1411         RemoveInputSource(isr);
1412         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1413     }
1414 }
1415
1416 void
1417 KeepAlive()
1418 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419     SendToICS("date\n");
1420     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1421 }
1422
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1425 {
1426         char buffer[MSG_SIZ], *args;
1427         
1428         args = (char *)&format + sizeof(format);
1429         vsnprintf(buffer, sizeof(buffer), format, args);
1430         buffer[sizeof(buffer)-1] = '\0';
1431         SendToICS(buffer);
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
1905     if( (int)lowestPiece >= BlackPawn ) {
1906         holdingsColumn = 0;
1907         countsColumn = 1;
1908         holdingsStartRow = BOARD_HEIGHT-1;
1909         direction = -1;
1910     } else {
1911         holdingsColumn = BOARD_WIDTH-1;
1912         countsColumn = BOARD_WIDTH-2;
1913         holdingsStartRow = 0;
1914         direction = 1;
1915     }
1916
1917     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1918         board[i][holdingsColumn] = EmptySquare;
1919         board[i][countsColumn]   = (ChessSquare) 0;
1920     }
1921     while( (p=*holdings++) != NULLCHAR ) {
1922         piece = CharToPiece( ToUpper(p) );
1923         if(piece == EmptySquare) continue;
1924         /*j = (int) piece - (int) WhitePawn;*/
1925         j = PieceToNumber(piece);
1926         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1927         if(j < 0) continue;               /* should not happen */
1928         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1929         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1930         board[holdingsStartRow+j*direction][countsColumn]++;
1931     }
1932
1933 }
1934
1935
1936 void
1937 VariantSwitch(Board board, VariantClass newVariant)
1938 {
1939    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1940    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1941 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
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             case VariantShogi:
1968               newWidth = 9;  newHeight = 9;
1969               gameInfo.holdingsSize = 7;
1970             case VariantBughouse:
1971             case VariantCrazyhouse:
1972               newHoldingsWidth = 2; break;
1973             default:
1974               newHoldingsWidth = gameInfo.holdingsSize = 0;
1975     }
1976
1977     if(newWidth  != gameInfo.boardWidth  ||
1978        newHeight != gameInfo.boardHeight ||
1979        newHoldingsWidth != gameInfo.holdingsWidth ) {
1980
1981         /* shift position to new playing area, if needed */
1982         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1983            for(i=0; i<BOARD_HEIGHT; i++) 
1984                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1985                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1986                                                      board[i][j];
1987            for(i=0; i<newHeight; i++) {
1988                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1989                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1990            }
1991         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1992            for(i=0; i<BOARD_HEIGHT; i++)
1993                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1994                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1995                                                  board[i][j];
1996         }
1997
1998         gameInfo.boardWidth  = newWidth;
1999         gameInfo.boardHeight = newHeight;
2000         gameInfo.holdingsWidth = newHoldingsWidth;
2001         gameInfo.variant = newVariant;
2002         InitDrawingSizes(-2, 0);
2003
2004         /* [HGM] The following should definitely be solved in a better way */
2005         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2006     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2007
2008     forwardMostMove = oldForwardMostMove;
2009     backwardMostMove = oldBackwardMostMove;
2010     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2011 }
2012
2013 static int loggedOn = FALSE;
2014
2015 /*-- Game start info cache: --*/
2016 int gs_gamenum;
2017 char gs_kind[MSG_SIZ];
2018 static char player1Name[128] = "";
2019 static char player2Name[128] = "";
2020 static int player1Rating = -1;
2021 static int player2Rating = -1;
2022 /*----------------------------*/
2023
2024 ColorClass curColor = ColorNormal;
2025 int suppressKibitz = 0;
2026
2027 void
2028 read_from_ics(isr, closure, data, count, error)
2029      InputSourceRef isr;
2030      VOIDSTAR closure;
2031      char *data;
2032      int count;
2033      int error;
2034 {
2035 #define BUF_SIZE 8192
2036 #define STARTED_NONE 0
2037 #define STARTED_MOVES 1
2038 #define STARTED_BOARD 2
2039 #define STARTED_OBSERVE 3
2040 #define STARTED_HOLDINGS 4
2041 #define STARTED_CHATTER 5
2042 #define STARTED_COMMENT 6
2043 #define STARTED_MOVES_NOHIDE 7
2044     
2045     static int started = STARTED_NONE;
2046     static char parse[20000];
2047     static int parse_pos = 0;
2048     static char buf[BUF_SIZE + 1];
2049     static int firstTime = TRUE, intfSet = FALSE;
2050     static ColorClass prevColor = ColorNormal;
2051     static int savingComment = FALSE;
2052     char str[500];
2053     int i, oldi;
2054     int buf_len;
2055     int next_out;
2056     int tkind;
2057     int backup;    /* [DM] For zippy color lines */
2058     char *p;
2059     char talker[MSG_SIZ]; // [HGM] chat
2060     int channel;
2061
2062     if (appData.debugMode) {
2063       if (!error) {
2064         fprintf(debugFP, "<ICS: ");
2065         show_bytes(debugFP, data, count);
2066         fprintf(debugFP, "\n");
2067       }
2068     }
2069
2070     if (appData.debugMode) { int f = forwardMostMove;
2071         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2072                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2073     }
2074     if (count > 0) {
2075         /* If last read ended with a partial line that we couldn't parse,
2076            prepend it to the new read and try again. */
2077         if (leftover_len > 0) {
2078             for (i=0; i<leftover_len; i++)
2079               buf[i] = buf[leftover_start + i];
2080         }
2081
2082         /* Copy in new characters, removing nulls and \r's */
2083         buf_len = leftover_len;
2084         for (i = 0; i < count; i++) {
2085             if (data[i] != NULLCHAR && data[i] != '\r')
2086               buf[buf_len++] = data[i];
2087             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2088                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2089                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2090                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2091                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2092             }
2093         }
2094
2095         buf[buf_len] = NULLCHAR;
2096         next_out = leftover_len;
2097         leftover_start = 0;
2098         
2099         i = 0;
2100         while (i < buf_len) {
2101             /* Deal with part of the TELNET option negotiation
2102                protocol.  We refuse to do anything beyond the
2103                defaults, except that we allow the WILL ECHO option,
2104                which ICS uses to turn off password echoing when we are
2105                directly connected to it.  We reject this option
2106                if localLineEditing mode is on (always on in xboard)
2107                and we are talking to port 23, which might be a real
2108                telnet server that will try to keep WILL ECHO on permanently.
2109              */
2110             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2111                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2112                 unsigned char option;
2113                 oldi = i;
2114                 switch ((unsigned char) buf[++i]) {
2115                   case TN_WILL:
2116                     if (appData.debugMode)
2117                       fprintf(debugFP, "\n<WILL ");
2118                     switch (option = (unsigned char) buf[++i]) {
2119                       case TN_ECHO:
2120                         if (appData.debugMode)
2121                           fprintf(debugFP, "ECHO ");
2122                         /* Reply only if this is a change, according
2123                            to the protocol rules. */
2124                         if (remoteEchoOption) break;
2125                         if (appData.localLineEditing &&
2126                             atoi(appData.icsPort) == TN_PORT) {
2127                             TelnetRequest(TN_DONT, TN_ECHO);
2128                         } else {
2129                             EchoOff();
2130                             TelnetRequest(TN_DO, TN_ECHO);
2131                             remoteEchoOption = TRUE;
2132                         }
2133                         break;
2134                       default:
2135                         if (appData.debugMode)
2136                           fprintf(debugFP, "%d ", option);
2137                         /* Whatever this is, we don't want it. */
2138                         TelnetRequest(TN_DONT, option);
2139                         break;
2140                     }
2141                     break;
2142                   case TN_WONT:
2143                     if (appData.debugMode)
2144                       fprintf(debugFP, "\n<WONT ");
2145                     switch (option = (unsigned char) buf[++i]) {
2146                       case TN_ECHO:
2147                         if (appData.debugMode)
2148                           fprintf(debugFP, "ECHO ");
2149                         /* Reply only if this is a change, according
2150                            to the protocol rules. */
2151                         if (!remoteEchoOption) break;
2152                         EchoOn();
2153                         TelnetRequest(TN_DONT, TN_ECHO);
2154                         remoteEchoOption = FALSE;
2155                         break;
2156                       default:
2157                         if (appData.debugMode)
2158                           fprintf(debugFP, "%d ", (unsigned char) option);
2159                         /* Whatever this is, it must already be turned
2160                            off, because we never agree to turn on
2161                            anything non-default, so according to the
2162                            protocol rules, we don't reply. */
2163                         break;
2164                     }
2165                     break;
2166                   case TN_DO:
2167                     if (appData.debugMode)
2168                       fprintf(debugFP, "\n<DO ");
2169                     switch (option = (unsigned char) buf[++i]) {
2170                       default:
2171                         /* Whatever this is, we refuse to do it. */
2172                         if (appData.debugMode)
2173                           fprintf(debugFP, "%d ", option);
2174                         TelnetRequest(TN_WONT, option);
2175                         break;
2176                     }
2177                     break;
2178                   case TN_DONT:
2179                     if (appData.debugMode)
2180                       fprintf(debugFP, "\n<DONT ");
2181                     switch (option = (unsigned char) buf[++i]) {
2182                       default:
2183                         if (appData.debugMode)
2184                           fprintf(debugFP, "%d ", option);
2185                         /* Whatever this is, we are already not doing
2186                            it, because we never agree to do anything
2187                            non-default, so according to the protocol
2188                            rules, we don't reply. */
2189                         break;
2190                     }
2191                     break;
2192                   case TN_IAC:
2193                     if (appData.debugMode)
2194                       fprintf(debugFP, "\n<IAC ");
2195                     /* Doubled IAC; pass it through */
2196                     i--;
2197                     break;
2198                   default:
2199                     if (appData.debugMode)
2200                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2201                     /* Drop all other telnet commands on the floor */
2202                     break;
2203                 }
2204                 if (oldi > next_out)
2205                   SendToPlayer(&buf[next_out], oldi - next_out);
2206                 if (++i > next_out)
2207                   next_out = i;
2208                 continue;
2209             }
2210                 
2211             /* OK, this at least will *usually* work */
2212             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2213                 loggedOn = TRUE;
2214             }
2215             
2216             if (loggedOn && !intfSet) {
2217                 if (ics_type == ICS_ICC) {
2218                   sprintf(str,
2219                           "/set-quietly interface %s\n/set-quietly style 12\n",
2220                           programVersion);
2221                   strcat(str, "/set-quietly wrap 0\n");
2222
2223                 } else if (ics_type == ICS_CHESSNET) {
2224                   sprintf(str, "/style 12\n");
2225                 } else {
2226                   strcpy(str, "alias $ @\n$set interface ");
2227                   strcat(str, programVersion);
2228                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2229 #ifdef WIN32
2230                   strcat(str, "$iset nohighlight 1\n");
2231 #endif
2232                   strcat(str, "$iset nowrap 1\n");
2233                   strcat(str, "$iset lock 1\n$style 12\n");
2234                 }
2235                 SendToICS(str);
2236                 NotifyFrontendLogin();
2237                 intfSet = TRUE;
2238             }
2239
2240             if (started == STARTED_COMMENT) {
2241                 /* Accumulate characters in comment */
2242                 parse[parse_pos++] = buf[i];
2243                 if (buf[i] == '\n') {
2244                     parse[parse_pos] = NULLCHAR;
2245                     if(chattingPartner>=0) {
2246                         char mess[MSG_SIZ];
2247                         sprintf(mess, "%s%s", talker, parse);
2248                         OutputChatMessage(chattingPartner, mess);
2249                         chattingPartner = -1;
2250                     } else
2251                     if(!suppressKibitz) // [HGM] kibitz
2252                         AppendComment(forwardMostMove, StripHighlight(parse));
2253                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2254                         int nrDigit = 0, nrAlph = 0, i;
2255                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2256                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2257                         parse[parse_pos] = NULLCHAR;
2258                         // try to be smart: if it does not look like search info, it should go to
2259                         // ICS interaction window after all, not to engine-output window.
2260                         for(i=0; i<parse_pos; i++) { // count letters and digits
2261                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2262                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2263                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2264                         }
2265                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2266                             int depth=0; float score;
2267                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2268                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2269                                 pvInfoList[forwardMostMove-1].depth = depth;
2270                                 pvInfoList[forwardMostMove-1].score = 100*score;
2271                             }
2272                             OutputKibitz(suppressKibitz, parse);
2273                         } else {
2274                             char tmp[MSG_SIZ];
2275                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2276                             SendToPlayer(tmp, strlen(tmp));
2277                         }
2278                     }
2279                     started = STARTED_NONE;
2280                 } else {
2281                     /* Don't match patterns against characters in chatter */
2282                     i++;
2283                     continue;
2284                 }
2285             }
2286             if (started == STARTED_CHATTER) {
2287                 if (buf[i] != '\n') {
2288                     /* Don't match patterns against characters in chatter */
2289                     i++;
2290                     continue;
2291                 }
2292                 started = STARTED_NONE;
2293             }
2294
2295             /* Kludge to deal with rcmd protocol */
2296             if (firstTime && looking_at(buf, &i, "\001*")) {
2297                 DisplayFatalError(&buf[1], 0, 1);
2298                 continue;
2299             } else {
2300                 firstTime = FALSE;
2301             }
2302
2303             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2304                 ics_type = ICS_ICC;
2305                 ics_prefix = "/";
2306                 if (appData.debugMode)
2307                   fprintf(debugFP, "ics_type %d\n", ics_type);
2308                 continue;
2309             }
2310             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2311                 ics_type = ICS_FICS;
2312                 ics_prefix = "$";
2313                 if (appData.debugMode)
2314                   fprintf(debugFP, "ics_type %d\n", ics_type);
2315                 continue;
2316             }
2317             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2318                 ics_type = ICS_CHESSNET;
2319                 ics_prefix = "/";
2320                 if (appData.debugMode)
2321                   fprintf(debugFP, "ics_type %d\n", ics_type);
2322                 continue;
2323             }
2324
2325             if (!loggedOn &&
2326                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2327                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2328                  looking_at(buf, &i, "will be \"*\""))) {
2329               strcpy(ics_handle, star_match[0]);
2330               continue;
2331             }
2332
2333             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2334               char buf[MSG_SIZ];
2335               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2336               DisplayIcsInteractionTitle(buf);
2337               have_set_title = TRUE;
2338             }
2339
2340             /* skip finger notes */
2341             if (started == STARTED_NONE &&
2342                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2343                  (buf[i] == '1' && buf[i+1] == '0')) &&
2344                 buf[i+2] == ':' && buf[i+3] == ' ') {
2345               started = STARTED_CHATTER;
2346               i += 3;
2347               continue;
2348             }
2349
2350             /* skip formula vars */
2351             if (started == STARTED_NONE &&
2352                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2353               started = STARTED_CHATTER;
2354               i += 3;
2355               continue;
2356             }
2357
2358             oldi = i;
2359             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2360             if (appData.autoKibitz && started == STARTED_NONE && 
2361                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2362                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2363                 if(looking_at(buf, &i, "* kibitzes: ") &&
2364                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2365                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2366                         suppressKibitz = TRUE;
2367                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2368                                 && (gameMode == IcsPlayingWhite)) ||
2369                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2370                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2371                             started = STARTED_CHATTER; // own kibitz we simply discard
2372                         else {
2373                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2374                             parse_pos = 0; parse[0] = NULLCHAR;
2375                             savingComment = TRUE;
2376                             suppressKibitz = gameMode != IcsObserving ? 2 :
2377                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2378                         } 
2379                         continue;
2380                 } else
2381                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2382                     started = STARTED_CHATTER;
2383                     suppressKibitz = TRUE;
2384                 }
2385             } // [HGM] kibitz: end of patch
2386
2387 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2388
2389             // [HGM] chat: intercept tells by users for which we have an open chat window
2390             channel = -1;
2391             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2392                                            looking_at(buf, &i, "* whispers:") ||
2393                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2394                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2395                 int p;
2396                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2397                 chattingPartner = -1;
2398
2399                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2400                 for(p=0; p<MAX_CHAT; p++) {
2401                     if(channel == atoi(chatPartner[p])) {
2402                     talker[0] = '['; strcat(talker, "]");
2403                     chattingPartner = p; break;
2404                     }
2405                 } else
2406                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2407                 for(p=0; p<MAX_CHAT; p++) {
2408                     if(!strcmp("WHISPER", chatPartner[p])) {
2409                         talker[0] = '['; strcat(talker, "]");
2410                         chattingPartner = p; break;
2411                     }
2412                 }
2413                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2414                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2415                     talker[0] = 0;
2416                     chattingPartner = p; break;
2417                 }
2418                 if(chattingPartner<0) i = oldi; else {
2419                     started = STARTED_COMMENT;
2420                     parse_pos = 0; parse[0] = NULLCHAR;
2421                     savingComment = TRUE;
2422                     suppressKibitz = TRUE;
2423                 }
2424             } // [HGM] chat: end of patch
2425
2426             if (appData.zippyTalk || appData.zippyPlay) {
2427                 /* [DM] Backup address for color zippy lines */
2428                 backup = i;
2429 #if ZIPPY
2430        #ifdef WIN32
2431                if (loggedOn == TRUE)
2432                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2433                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2434        #else
2435                 if (ZippyControl(buf, &i) ||
2436                     ZippyConverse(buf, &i) ||
2437                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2438                       loggedOn = TRUE;
2439                       if (!appData.colorize) continue;
2440                 }
2441        #endif
2442 #endif
2443             } // [DM] 'else { ' deleted
2444                 if (
2445                     /* Regular tells and says */
2446                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2447                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2448                     looking_at(buf, &i, "* says: ") ||
2449                     /* Don't color "message" or "messages" output */
2450                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2451                     looking_at(buf, &i, "*. * at *:*: ") ||
2452                     looking_at(buf, &i, "--* (*:*): ") ||
2453                     /* Message notifications (same color as tells) */
2454                     looking_at(buf, &i, "* has left a message ") ||
2455                     looking_at(buf, &i, "* just sent you a message:\n") ||
2456                     /* Whispers and kibitzes */
2457                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2458                     looking_at(buf, &i, "* kibitzes: ") ||
2459                     /* Channel tells */
2460                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2461
2462                   if (tkind == 1 && strchr(star_match[0], ':')) {
2463                       /* Avoid "tells you:" spoofs in channels */
2464                      tkind = 3;
2465                   }
2466                   if (star_match[0][0] == NULLCHAR ||
2467                       strchr(star_match[0], ' ') ||
2468                       (tkind == 3 && strchr(star_match[1], ' '))) {
2469                     /* Reject bogus matches */
2470                     i = oldi;
2471                   } else {
2472                     if (appData.colorize) {
2473                       if (oldi > next_out) {
2474                         SendToPlayer(&buf[next_out], oldi - next_out);
2475                         next_out = oldi;
2476                       }
2477                       switch (tkind) {
2478                       case 1:
2479                         Colorize(ColorTell, FALSE);
2480                         curColor = ColorTell;
2481                         break;
2482                       case 2:
2483                         Colorize(ColorKibitz, FALSE);
2484                         curColor = ColorKibitz;
2485                         break;
2486                       case 3:
2487                         p = strrchr(star_match[1], '(');
2488                         if (p == NULL) {
2489                           p = star_match[1];
2490                         } else {
2491                           p++;
2492                         }
2493                         if (atoi(p) == 1) {
2494                           Colorize(ColorChannel1, FALSE);
2495                           curColor = ColorChannel1;
2496                         } else {
2497                           Colorize(ColorChannel, FALSE);
2498                           curColor = ColorChannel;
2499                         }
2500                         break;
2501                       case 5:
2502                         curColor = ColorNormal;
2503                         break;
2504                       }
2505                     }
2506                     if (started == STARTED_NONE && appData.autoComment &&
2507                         (gameMode == IcsObserving ||
2508                          gameMode == IcsPlayingWhite ||
2509                          gameMode == IcsPlayingBlack)) {
2510                       parse_pos = i - oldi;
2511                       memcpy(parse, &buf[oldi], parse_pos);
2512                       parse[parse_pos] = NULLCHAR;
2513                       started = STARTED_COMMENT;
2514                       savingComment = TRUE;
2515                     } else {
2516                       started = STARTED_CHATTER;
2517                       savingComment = FALSE;
2518                     }
2519                     loggedOn = TRUE;
2520                     continue;
2521                   }
2522                 }
2523
2524                 if (looking_at(buf, &i, "* s-shouts: ") ||
2525                     looking_at(buf, &i, "* c-shouts: ")) {
2526                     if (appData.colorize) {
2527                         if (oldi > next_out) {
2528                             SendToPlayer(&buf[next_out], oldi - next_out);
2529                             next_out = oldi;
2530                         }
2531                         Colorize(ColorSShout, FALSE);
2532                         curColor = ColorSShout;
2533                     }
2534                     loggedOn = TRUE;
2535                     started = STARTED_CHATTER;
2536                     continue;
2537                 }
2538
2539                 if (looking_at(buf, &i, "--->")) {
2540                     loggedOn = TRUE;
2541                     continue;
2542                 }
2543
2544                 if (looking_at(buf, &i, "* shouts: ") ||
2545                     looking_at(buf, &i, "--> ")) {
2546                     if (appData.colorize) {
2547                         if (oldi > next_out) {
2548                             SendToPlayer(&buf[next_out], oldi - next_out);
2549                             next_out = oldi;
2550                         }
2551                         Colorize(ColorShout, FALSE);
2552                         curColor = ColorShout;
2553                     }
2554                     loggedOn = TRUE;
2555                     started = STARTED_CHATTER;
2556                     continue;
2557                 }
2558
2559                 if (looking_at( buf, &i, "Challenge:")) {
2560                     if (appData.colorize) {
2561                         if (oldi > next_out) {
2562                             SendToPlayer(&buf[next_out], oldi - next_out);
2563                             next_out = oldi;
2564                         }
2565                         Colorize(ColorChallenge, FALSE);
2566                         curColor = ColorChallenge;
2567                     }
2568                     loggedOn = TRUE;
2569                     continue;
2570                 }
2571
2572                 if (looking_at(buf, &i, "* offers you") ||
2573                     looking_at(buf, &i, "* offers to be") ||
2574                     looking_at(buf, &i, "* would like to") ||
2575                     looking_at(buf, &i, "* requests to") ||
2576                     looking_at(buf, &i, "Your opponent offers") ||
2577                     looking_at(buf, &i, "Your opponent requests")) {
2578
2579                     if (appData.colorize) {
2580                         if (oldi > next_out) {
2581                             SendToPlayer(&buf[next_out], oldi - next_out);
2582                             next_out = oldi;
2583                         }
2584                         Colorize(ColorRequest, FALSE);
2585                         curColor = ColorRequest;
2586                     }
2587                     continue;
2588                 }
2589
2590                 if (looking_at(buf, &i, "* (*) seeking")) {
2591                     if (appData.colorize) {
2592                         if (oldi > next_out) {
2593                             SendToPlayer(&buf[next_out], oldi - next_out);
2594                             next_out = oldi;
2595                         }
2596                         Colorize(ColorSeek, FALSE);
2597                         curColor = ColorSeek;
2598                     }
2599                     continue;
2600             }
2601
2602             if (looking_at(buf, &i, "\\   ")) {
2603                 if (prevColor != ColorNormal) {
2604                     if (oldi > next_out) {
2605                         SendToPlayer(&buf[next_out], oldi - next_out);
2606                         next_out = oldi;
2607                     }
2608                     Colorize(prevColor, TRUE);
2609                     curColor = prevColor;
2610                 }
2611                 if (savingComment) {
2612                     parse_pos = i - oldi;
2613                     memcpy(parse, &buf[oldi], parse_pos);
2614                     parse[parse_pos] = NULLCHAR;
2615                     started = STARTED_COMMENT;
2616                 } else {
2617                     started = STARTED_CHATTER;
2618                 }
2619                 continue;
2620             }
2621
2622             if (looking_at(buf, &i, "Black Strength :") ||
2623                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2624                 looking_at(buf, &i, "<10>") ||
2625                 looking_at(buf, &i, "#@#")) {
2626                 /* Wrong board style */
2627                 loggedOn = TRUE;
2628                 SendToICS(ics_prefix);
2629                 SendToICS("set style 12\n");
2630                 SendToICS(ics_prefix);
2631                 SendToICS("refresh\n");
2632                 continue;
2633             }
2634             
2635             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2636                 ICSInitScript();
2637                 have_sent_ICS_logon = 1;
2638                 continue;
2639             }
2640               
2641             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2642                 (looking_at(buf, &i, "\n<12> ") ||
2643                  looking_at(buf, &i, "<12> "))) {
2644                 loggedOn = TRUE;
2645                 if (oldi > next_out) {
2646                     SendToPlayer(&buf[next_out], oldi - next_out);
2647                 }
2648                 next_out = i;
2649                 started = STARTED_BOARD;
2650                 parse_pos = 0;
2651                 continue;
2652             }
2653
2654             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2655                 looking_at(buf, &i, "<b1> ")) {
2656                 if (oldi > next_out) {
2657                     SendToPlayer(&buf[next_out], oldi - next_out);
2658                 }
2659                 next_out = i;
2660                 started = STARTED_HOLDINGS;
2661                 parse_pos = 0;
2662                 continue;
2663             }
2664
2665             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2666                 loggedOn = TRUE;
2667                 /* Header for a move list -- first line */
2668
2669                 switch (ics_getting_history) {
2670                   case H_FALSE:
2671                     switch (gameMode) {
2672                       case IcsIdle:
2673                       case BeginningOfGame:
2674                         /* User typed "moves" or "oldmoves" while we
2675                            were idle.  Pretend we asked for these
2676                            moves and soak them up so user can step
2677                            through them and/or save them.
2678                            */
2679                         Reset(FALSE, TRUE);
2680                         gameMode = IcsObserving;
2681                         ModeHighlight();
2682                         ics_gamenum = -1;
2683                         ics_getting_history = H_GOT_UNREQ_HEADER;
2684                         break;
2685                       case EditGame: /*?*/
2686                       case EditPosition: /*?*/
2687                         /* Should above feature work in these modes too? */
2688                         /* For now it doesn't */
2689                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2690                         break;
2691                       default:
2692                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2693                         break;
2694                     }
2695                     break;
2696                   case H_REQUESTED:
2697                     /* Is this the right one? */
2698                     if (gameInfo.white && gameInfo.black &&
2699                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2700                         strcmp(gameInfo.black, star_match[2]) == 0) {
2701                         /* All is well */
2702                         ics_getting_history = H_GOT_REQ_HEADER;
2703                     }
2704                     break;
2705                   case H_GOT_REQ_HEADER:
2706                   case H_GOT_UNREQ_HEADER:
2707                   case H_GOT_UNWANTED_HEADER:
2708                   case H_GETTING_MOVES:
2709                     /* Should not happen */
2710                     DisplayError(_("Error gathering move list: two headers"), 0);
2711                     ics_getting_history = H_FALSE;
2712                     break;
2713                 }
2714
2715                 /* Save player ratings into gameInfo if needed */
2716                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2717                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2718                     (gameInfo.whiteRating == -1 ||
2719                      gameInfo.blackRating == -1)) {
2720
2721                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2722                     gameInfo.blackRating = string_to_rating(star_match[3]);
2723                     if (appData.debugMode)
2724                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2725                               gameInfo.whiteRating, gameInfo.blackRating);
2726                 }
2727                 continue;
2728             }
2729
2730             if (looking_at(buf, &i,
2731               "* * match, initial time: * minute*, increment: * second")) {
2732                 /* Header for a move list -- second line */
2733                 /* Initial board will follow if this is a wild game */
2734                 if (gameInfo.event != NULL) free(gameInfo.event);
2735                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2736                 gameInfo.event = StrSave(str);
2737                 /* [HGM] we switched variant. Translate boards if needed. */
2738                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2739                 continue;
2740             }
2741
2742             if (looking_at(buf, &i, "Move  ")) {
2743                 /* Beginning of a move list */
2744                 switch (ics_getting_history) {
2745                   case H_FALSE:
2746                     /* Normally should not happen */
2747                     /* Maybe user hit reset while we were parsing */
2748                     break;
2749                   case H_REQUESTED:
2750                     /* Happens if we are ignoring a move list that is not
2751                      * the one we just requested.  Common if the user
2752                      * tries to observe two games without turning off
2753                      * getMoveList */
2754                     break;
2755                   case H_GETTING_MOVES:
2756                     /* Should not happen */
2757                     DisplayError(_("Error gathering move list: nested"), 0);
2758                     ics_getting_history = H_FALSE;
2759                     break;
2760                   case H_GOT_REQ_HEADER:
2761                     ics_getting_history = H_GETTING_MOVES;
2762                     started = STARTED_MOVES;
2763                     parse_pos = 0;
2764                     if (oldi > next_out) {
2765                         SendToPlayer(&buf[next_out], oldi - next_out);
2766                     }
2767                     break;
2768                   case H_GOT_UNREQ_HEADER:
2769                     ics_getting_history = H_GETTING_MOVES;
2770                     started = STARTED_MOVES_NOHIDE;
2771                     parse_pos = 0;
2772                     break;
2773                   case H_GOT_UNWANTED_HEADER:
2774                     ics_getting_history = H_FALSE;
2775                     break;
2776                 }
2777                 continue;
2778             }                           
2779             
2780             if (looking_at(buf, &i, "% ") ||
2781                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2782                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2783                 savingComment = FALSE;
2784                 switch (started) {
2785                   case STARTED_MOVES:
2786                   case STARTED_MOVES_NOHIDE:
2787                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2788                     parse[parse_pos + i - oldi] = NULLCHAR;
2789                     ParseGameHistory(parse);
2790 #if ZIPPY
2791                     if (appData.zippyPlay && first.initDone) {
2792                         FeedMovesToProgram(&first, forwardMostMove);
2793                         if (gameMode == IcsPlayingWhite) {
2794                             if (WhiteOnMove(forwardMostMove)) {
2795                                 if (first.sendTime) {
2796                                   if (first.useColors) {
2797                                     SendToProgram("black\n", &first); 
2798                                   }
2799                                   SendTimeRemaining(&first, TRUE);
2800                                 }
2801                                 if (first.useColors) {
2802                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2803                                 }
2804                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2805                                 first.maybeThinking = TRUE;
2806                             } else {
2807                                 if (first.usePlayother) {
2808                                   if (first.sendTime) {
2809                                     SendTimeRemaining(&first, TRUE);
2810                                   }
2811                                   SendToProgram("playother\n", &first);
2812                                   firstMove = FALSE;
2813                                 } else {
2814                                   firstMove = TRUE;
2815                                 }
2816                             }
2817                         } else if (gameMode == IcsPlayingBlack) {
2818                             if (!WhiteOnMove(forwardMostMove)) {
2819                                 if (first.sendTime) {
2820                                   if (first.useColors) {
2821                                     SendToProgram("white\n", &first);
2822                                   }
2823                                   SendTimeRemaining(&first, FALSE);
2824                                 }
2825                                 if (first.useColors) {
2826                                   SendToProgram("black\n", &first);
2827                                 }
2828                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2829                                 first.maybeThinking = TRUE;
2830                             } else {
2831                                 if (first.usePlayother) {
2832                                   if (first.sendTime) {
2833                                     SendTimeRemaining(&first, FALSE);
2834                                   }
2835                                   SendToProgram("playother\n", &first);
2836                                   firstMove = FALSE;
2837                                 } else {
2838                                   firstMove = TRUE;
2839                                 }
2840                             }
2841                         }                       
2842                     }
2843 #endif
2844                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2845                         /* Moves came from oldmoves or moves command
2846                            while we weren't doing anything else.
2847                            */
2848                         currentMove = forwardMostMove;
2849                         ClearHighlights();/*!!could figure this out*/
2850                         flipView = appData.flipView;
2851                         DrawPosition(FALSE, boards[currentMove]);
2852                         DisplayBothClocks();
2853                         sprintf(str, "%s vs. %s",
2854                                 gameInfo.white, gameInfo.black);
2855                         DisplayTitle(str);
2856                         gameMode = IcsIdle;
2857                     } else {
2858                         /* Moves were history of an active game */
2859                         if (gameInfo.resultDetails != NULL) {
2860                             free(gameInfo.resultDetails);
2861                             gameInfo.resultDetails = NULL;
2862                         }
2863                     }
2864                     HistorySet(parseList, backwardMostMove,
2865                                forwardMostMove, currentMove-1);
2866                     DisplayMove(currentMove - 1);
2867                     if (started == STARTED_MOVES) next_out = i;
2868                     started = STARTED_NONE;
2869                     ics_getting_history = H_FALSE;
2870                     break;
2871
2872                   case STARTED_OBSERVE:
2873                     started = STARTED_NONE;
2874                     SendToICS(ics_prefix);
2875                     SendToICS("refresh\n");
2876                     break;
2877
2878                   default:
2879                     break;
2880                 }
2881                 if(bookHit) { // [HGM] book: simulate book reply
2882                     static char bookMove[MSG_SIZ]; // a bit generous?
2883
2884                     programStats.nodes = programStats.depth = programStats.time = 
2885                     programStats.score = programStats.got_only_move = 0;
2886                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2887
2888                     strcpy(bookMove, "move ");
2889                     strcat(bookMove, bookHit);
2890                     HandleMachineMove(bookMove, &first);
2891                 }
2892                 continue;
2893             }
2894             
2895             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2896                  started == STARTED_HOLDINGS ||
2897                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2898                 /* Accumulate characters in move list or board */
2899                 parse[parse_pos++] = buf[i];
2900             }
2901             
2902             /* Start of game messages.  Mostly we detect start of game
2903                when the first board image arrives.  On some versions
2904                of the ICS, though, we need to do a "refresh" after starting
2905                to observe in order to get the current board right away. */
2906             if (looking_at(buf, &i, "Adding game * to observation list")) {
2907                 started = STARTED_OBSERVE;
2908                 continue;
2909             }
2910
2911             /* Handle auto-observe */
2912             if (appData.autoObserve &&
2913                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2914                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2915                 char *player;
2916                 /* Choose the player that was highlighted, if any. */
2917                 if (star_match[0][0] == '\033' ||
2918                     star_match[1][0] != '\033') {
2919                     player = star_match[0];
2920                 } else {
2921                     player = star_match[2];
2922                 }
2923                 sprintf(str, "%sobserve %s\n",
2924                         ics_prefix, StripHighlightAndTitle(player));
2925                 SendToICS(str);
2926
2927                 /* Save ratings from notify string */
2928                 strcpy(player1Name, star_match[0]);
2929                 player1Rating = string_to_rating(star_match[1]);
2930                 strcpy(player2Name, star_match[2]);
2931                 player2Rating = string_to_rating(star_match[3]);
2932
2933                 if (appData.debugMode)
2934                   fprintf(debugFP, 
2935                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2936                           player1Name, player1Rating,
2937                           player2Name, player2Rating);
2938
2939                 continue;
2940             }
2941
2942             /* Deal with automatic examine mode after a game,
2943                and with IcsObserving -> IcsExamining transition */
2944             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2945                 looking_at(buf, &i, "has made you an examiner of game *")) {
2946
2947                 int gamenum = atoi(star_match[0]);
2948                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2949                     gamenum == ics_gamenum) {
2950                     /* We were already playing or observing this game;
2951                        no need to refetch history */
2952                     gameMode = IcsExamining;
2953                     if (pausing) {
2954                         pauseExamForwardMostMove = forwardMostMove;
2955                     } else if (currentMove < forwardMostMove) {
2956                         ForwardInner(forwardMostMove);
2957                     }
2958                 } else {
2959                     /* I don't think this case really can happen */
2960                     SendToICS(ics_prefix);
2961                     SendToICS("refresh\n");
2962                 }
2963                 continue;
2964             }    
2965             
2966             /* Error messages */
2967 //          if (ics_user_moved) {
2968             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2969                 if (looking_at(buf, &i, "Illegal move") ||
2970                     looking_at(buf, &i, "Not a legal move") ||
2971                     looking_at(buf, &i, "Your king is in check") ||
2972                     looking_at(buf, &i, "It isn't your turn") ||
2973                     looking_at(buf, &i, "It is not your move")) {
2974                     /* Illegal move */
2975                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2976                         currentMove = --forwardMostMove;
2977                         DisplayMove(currentMove - 1); /* before DMError */
2978                         DrawPosition(FALSE, boards[currentMove]);
2979                         SwitchClocks();
2980                         DisplayBothClocks();
2981                     }
2982                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2983                     ics_user_moved = 0;
2984                     continue;
2985                 }
2986             }
2987
2988             if (looking_at(buf, &i, "still have time") ||
2989                 looking_at(buf, &i, "not out of time") ||
2990                 looking_at(buf, &i, "either player is out of time") ||
2991                 looking_at(buf, &i, "has timeseal; checking")) {
2992                 /* We must have called his flag a little too soon */
2993                 whiteFlag = blackFlag = FALSE;
2994                 continue;
2995             }
2996
2997             if (looking_at(buf, &i, "added * seconds to") ||
2998                 looking_at(buf, &i, "seconds were added to")) {
2999                 /* Update the clocks */
3000                 SendToICS(ics_prefix);
3001                 SendToICS("refresh\n");
3002                 continue;
3003             }
3004
3005             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3006                 ics_clock_paused = TRUE;
3007                 StopClocks();
3008                 continue;
3009             }
3010
3011             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3012                 ics_clock_paused = FALSE;
3013                 StartClocks();
3014                 continue;
3015             }
3016
3017             /* Grab player ratings from the Creating: message.
3018                Note we have to check for the special case when
3019                the ICS inserts things like [white] or [black]. */
3020             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3021                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3022                 /* star_matches:
3023                    0    player 1 name (not necessarily white)
3024                    1    player 1 rating
3025                    2    empty, white, or black (IGNORED)
3026                    3    player 2 name (not necessarily black)
3027                    4    player 2 rating
3028                    
3029                    The names/ratings are sorted out when the game
3030                    actually starts (below).
3031                 */
3032                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3033                 player1Rating = string_to_rating(star_match[1]);
3034                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3035                 player2Rating = string_to_rating(star_match[4]);
3036
3037                 if (appData.debugMode)
3038                   fprintf(debugFP, 
3039                           "Ratings from 'Creating:' %s %d, %s %d\n",
3040                           player1Name, player1Rating,
3041                           player2Name, player2Rating);
3042
3043                 continue;
3044             }
3045             
3046             /* Improved generic start/end-of-game messages */
3047             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3048                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3049                 /* If tkind == 0: */
3050                 /* star_match[0] is the game number */
3051                 /*           [1] is the white player's name */
3052                 /*           [2] is the black player's name */
3053                 /* For end-of-game: */
3054                 /*           [3] is the reason for the game end */
3055                 /*           [4] is a PGN end game-token, preceded by " " */
3056                 /* For start-of-game: */
3057                 /*           [3] begins with "Creating" or "Continuing" */
3058                 /*           [4] is " *" or empty (don't care). */
3059                 int gamenum = atoi(star_match[0]);
3060                 char *whitename, *blackname, *why, *endtoken;
3061                 ChessMove endtype = (ChessMove) 0;
3062
3063                 if (tkind == 0) {
3064                   whitename = star_match[1];
3065                   blackname = star_match[2];
3066                   why = star_match[3];
3067                   endtoken = star_match[4];
3068                 } else {
3069                   whitename = star_match[1];
3070                   blackname = star_match[3];
3071                   why = star_match[5];
3072                   endtoken = star_match[6];
3073                 }
3074
3075                 /* Game start messages */
3076                 if (strncmp(why, "Creating ", 9) == 0 ||
3077                     strncmp(why, "Continuing ", 11) == 0) {
3078                     gs_gamenum = gamenum;
3079                     strcpy(gs_kind, strchr(why, ' ') + 1);
3080 #if ZIPPY
3081                     if (appData.zippyPlay) {
3082                         ZippyGameStart(whitename, blackname);
3083                     }
3084 #endif /*ZIPPY*/
3085                     continue;
3086                 }
3087
3088                 /* Game end messages */
3089                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3090                     ics_gamenum != gamenum) {
3091                     continue;
3092                 }
3093                 while (endtoken[0] == ' ') endtoken++;
3094                 switch (endtoken[0]) {
3095                   case '*':
3096                   default:
3097                     endtype = GameUnfinished;
3098                     break;
3099                   case '0':
3100                     endtype = BlackWins;
3101                     break;
3102                   case '1':
3103                     if (endtoken[1] == '/')
3104                       endtype = GameIsDrawn;
3105                     else
3106                       endtype = WhiteWins;
3107                     break;
3108                 }
3109                 GameEnds(endtype, why, GE_ICS);
3110 #if ZIPPY
3111                 if (appData.zippyPlay && first.initDone) {
3112                     ZippyGameEnd(endtype, why);
3113                     if (first.pr == NULL) {
3114                       /* Start the next process early so that we'll
3115                          be ready for the next challenge */
3116                       StartChessProgram(&first);
3117                     }
3118                     /* Send "new" early, in case this command takes
3119                        a long time to finish, so that we'll be ready
3120                        for the next challenge. */
3121                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3122                     Reset(TRUE, TRUE);
3123                 }
3124 #endif /*ZIPPY*/
3125                 continue;
3126             }
3127
3128             if (looking_at(buf, &i, "Removing game * from observation") ||
3129                 looking_at(buf, &i, "no longer observing game *") ||
3130                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3131                 if (gameMode == IcsObserving &&
3132                     atoi(star_match[0]) == ics_gamenum)
3133                   {
3134                       /* icsEngineAnalyze */
3135                       if (appData.icsEngineAnalyze) {
3136                             ExitAnalyzeMode();
3137                             ModeHighlight();
3138                       }
3139                       StopClocks();
3140                       gameMode = IcsIdle;
3141                       ics_gamenum = -1;
3142                       ics_user_moved = FALSE;
3143                   }
3144                 continue;
3145             }
3146
3147             if (looking_at(buf, &i, "no longer examining game *")) {
3148                 if (gameMode == IcsExamining &&
3149                     atoi(star_match[0]) == ics_gamenum)
3150                   {
3151                       gameMode = IcsIdle;
3152                       ics_gamenum = -1;
3153                       ics_user_moved = FALSE;
3154                   }
3155                 continue;
3156             }
3157
3158             /* Advance leftover_start past any newlines we find,
3159                so only partial lines can get reparsed */
3160             if (looking_at(buf, &i, "\n")) {
3161                 prevColor = curColor;
3162                 if (curColor != ColorNormal) {
3163                     if (oldi > next_out) {
3164                         SendToPlayer(&buf[next_out], oldi - next_out);
3165                         next_out = oldi;
3166                     }
3167                     Colorize(ColorNormal, FALSE);
3168                     curColor = ColorNormal;
3169                 }
3170                 if (started == STARTED_BOARD) {
3171                     started = STARTED_NONE;
3172                     parse[parse_pos] = NULLCHAR;
3173                     ParseBoard12(parse);
3174                     ics_user_moved = 0;
3175
3176                     /* Send premove here */
3177                     if (appData.premove) {
3178                       char str[MSG_SIZ];
3179                       if (currentMove == 0 &&
3180                           gameMode == IcsPlayingWhite &&
3181                           appData.premoveWhite) {
3182                         sprintf(str, "%s%s\n", ics_prefix,
3183                                 appData.premoveWhiteText);
3184                         if (appData.debugMode)
3185                           fprintf(debugFP, "Sending premove:\n");
3186                         SendToICS(str);
3187                       } else if (currentMove == 1 &&
3188                                  gameMode == IcsPlayingBlack &&
3189                                  appData.premoveBlack) {
3190                         sprintf(str, "%s%s\n", ics_prefix,
3191                                 appData.premoveBlackText);
3192                         if (appData.debugMode)
3193                           fprintf(debugFP, "Sending premove:\n");
3194                         SendToICS(str);
3195                       } else if (gotPremove) {
3196                         gotPremove = 0;
3197                         ClearPremoveHighlights();
3198                         if (appData.debugMode)
3199                           fprintf(debugFP, "Sending premove:\n");
3200                           UserMoveEvent(premoveFromX, premoveFromY, 
3201                                         premoveToX, premoveToY, 
3202                                         premovePromoChar);
3203                       }
3204                     }
3205
3206                     /* Usually suppress following prompt */
3207                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3208                         if (looking_at(buf, &i, "*% ")) {
3209                             savingComment = FALSE;
3210                         }
3211                     }
3212                     next_out = i;
3213                 } else if (started == STARTED_HOLDINGS) {
3214                     int gamenum;
3215                     char new_piece[MSG_SIZ];
3216                     started = STARTED_NONE;
3217                     parse[parse_pos] = NULLCHAR;
3218                     if (appData.debugMode)
3219                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3220                                                         parse, currentMove);
3221                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3222                         gamenum == ics_gamenum) {
3223                         if (gameInfo.variant == VariantNormal) {
3224                           /* [HGM] We seem to switch variant during a game!
3225                            * Presumably no holdings were displayed, so we have
3226                            * to move the position two files to the right to
3227                            * create room for them!
3228                            */
3229                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3230                           /* Get a move list just to see the header, which
3231                              will tell us whether this is really bug or zh */
3232                           if (ics_getting_history == H_FALSE) {
3233                             ics_getting_history = H_REQUESTED;
3234                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3235                             SendToICS(str);
3236                           }
3237                         }
3238                         new_piece[0] = NULLCHAR;
3239                         sscanf(parse, "game %d white [%s black [%s <- %s",
3240                                &gamenum, white_holding, black_holding,
3241                                new_piece);
3242                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3243                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3244                         /* [HGM] copy holdings to board holdings area */
3245                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3246                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3247 #if ZIPPY
3248                         if (appData.zippyPlay && first.initDone) {
3249                             ZippyHoldings(white_holding, black_holding,
3250                                           new_piece);
3251                         }
3252 #endif /*ZIPPY*/
3253                         if (tinyLayout || smallLayout) {
3254                             char wh[16], bh[16];
3255                             PackHolding(wh, white_holding);
3256                             PackHolding(bh, black_holding);
3257                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3258                                     gameInfo.white, gameInfo.black);
3259                         } else {
3260                             sprintf(str, "%s [%s] vs. %s [%s]",
3261                                     gameInfo.white, white_holding,
3262                                     gameInfo.black, black_holding);
3263                         }
3264
3265                         DrawPosition(FALSE, boards[currentMove]);
3266                         DisplayTitle(str);
3267                     }
3268                     /* Suppress following prompt */
3269                     if (looking_at(buf, &i, "*% ")) {
3270                         savingComment = FALSE;
3271                     }
3272                     next_out = i;
3273                 }
3274                 continue;
3275             }
3276
3277             i++;                /* skip unparsed character and loop back */
3278         }
3279         
3280         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3281             started != STARTED_HOLDINGS && i > next_out) {
3282             SendToPlayer(&buf[next_out], i - next_out);
3283             next_out = i;
3284         }
3285         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3286         
3287         leftover_len = buf_len - leftover_start;
3288         /* if buffer ends with something we couldn't parse,
3289            reparse it after appending the next read */
3290         
3291     } else if (count == 0) {
3292         RemoveInputSource(isr);
3293         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3294     } else {
3295         DisplayFatalError(_("Error reading from ICS"), error, 1);
3296     }
3297 }
3298
3299
3300 /* Board style 12 looks like this:
3301    
3302    <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
3303    
3304  * The "<12> " is stripped before it gets to this routine.  The two
3305  * trailing 0's (flip state and clock ticking) are later addition, and
3306  * some chess servers may not have them, or may have only the first.
3307  * Additional trailing fields may be added in the future.  
3308  */
3309
3310 #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"
3311
3312 #define RELATION_OBSERVING_PLAYED    0
3313 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3314 #define RELATION_PLAYING_MYMOVE      1
3315 #define RELATION_PLAYING_NOTMYMOVE  -1
3316 #define RELATION_EXAMINING           2
3317 #define RELATION_ISOLATED_BOARD     -3
3318 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3319
3320 void
3321 ParseBoard12(string)
3322      char *string;
3323
3324     GameMode newGameMode;
3325     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3326     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3327     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3328     char to_play, board_chars[200];
3329     char move_str[500], str[500], elapsed_time[500];
3330     char black[32], white[32];
3331     Board board;
3332     int prevMove = currentMove;
3333     int ticking = 2;
3334     ChessMove moveType;
3335     int fromX, fromY, toX, toY;
3336     char promoChar;
3337     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3338     char *bookHit = NULL; // [HGM] book
3339
3340     fromX = fromY = toX = toY = -1;
3341     
3342     newGame = FALSE;
3343
3344     if (appData.debugMode)
3345       fprintf(debugFP, _("Parsing board: %s\n"), string);
3346
3347     move_str[0] = NULLCHAR;
3348     elapsed_time[0] = NULLCHAR;
3349     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3350         int  i = 0, j;
3351         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3352             if(string[i] == ' ') { ranks++; files = 0; }
3353             else files++;
3354             i++;
3355         }
3356         for(j = 0; j <i; j++) board_chars[j] = string[j];
3357         board_chars[i] = '\0';
3358         string += i + 1;
3359     }
3360     n = sscanf(string, PATTERN, &to_play, &double_push,
3361                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3362                &gamenum, white, black, &relation, &basetime, &increment,
3363                &white_stren, &black_stren, &white_time, &black_time,
3364                &moveNum, str, elapsed_time, move_str, &ics_flip,
3365                &ticking);
3366
3367     if (n < 21) {
3368         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3369         DisplayError(str, 0);
3370         return;
3371     }
3372
3373     /* Convert the move number to internal form */
3374     moveNum = (moveNum - 1) * 2;
3375     if (to_play == 'B') moveNum++;
3376     if (moveNum >= MAX_MOVES) {
3377       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3378                         0, 1);
3379       return;
3380     }
3381     
3382     switch (relation) {
3383       case RELATION_OBSERVING_PLAYED:
3384       case RELATION_OBSERVING_STATIC:
3385         if (gamenum == -1) {
3386             /* Old ICC buglet */
3387             relation = RELATION_OBSERVING_STATIC;
3388         }
3389         newGameMode = IcsObserving;
3390         break;
3391       case RELATION_PLAYING_MYMOVE:
3392       case RELATION_PLAYING_NOTMYMOVE:
3393         newGameMode =
3394           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3395             IcsPlayingWhite : IcsPlayingBlack;
3396         break;
3397       case RELATION_EXAMINING:
3398         newGameMode = IcsExamining;
3399         break;
3400       case RELATION_ISOLATED_BOARD:
3401       default:
3402         /* Just display this board.  If user was doing something else,
3403            we will forget about it until the next board comes. */ 
3404         newGameMode = IcsIdle;
3405         break;
3406       case RELATION_STARTING_POSITION:
3407         newGameMode = gameMode;
3408         break;
3409     }
3410     
3411     /* Modify behavior for initial board display on move listing
3412        of wild games.
3413        */
3414     switch (ics_getting_history) {
3415       case H_FALSE:
3416       case H_REQUESTED:
3417         break;
3418       case H_GOT_REQ_HEADER:
3419       case H_GOT_UNREQ_HEADER:
3420         /* This is the initial position of the current game */
3421         gamenum = ics_gamenum;
3422         moveNum = 0;            /* old ICS bug workaround */
3423         if (to_play == 'B') {
3424           startedFromSetupPosition = TRUE;
3425           blackPlaysFirst = TRUE;
3426           moveNum = 1;
3427           if (forwardMostMove == 0) forwardMostMove = 1;
3428           if (backwardMostMove == 0) backwardMostMove = 1;
3429           if (currentMove == 0) currentMove = 1;
3430         }
3431         newGameMode = gameMode;
3432         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3433         break;
3434       case H_GOT_UNWANTED_HEADER:
3435         /* This is an initial board that we don't want */
3436         return;
3437       case H_GETTING_MOVES:
3438         /* Should not happen */
3439         DisplayError(_("Error gathering move list: extra board"), 0);
3440         ics_getting_history = H_FALSE;
3441         return;
3442     }
3443     
3444     /* Take action if this is the first board of a new game, or of a
3445        different game than is currently being displayed.  */
3446     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3447         relation == RELATION_ISOLATED_BOARD) {
3448         
3449         /* Forget the old game and get the history (if any) of the new one */
3450         if (gameMode != BeginningOfGame) {
3451           Reset(FALSE, TRUE);
3452         }
3453         newGame = TRUE;
3454         if (appData.autoRaiseBoard) BoardToTop();
3455         prevMove = -3;
3456         if (gamenum == -1) {
3457             newGameMode = IcsIdle;
3458         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3459                    appData.getMoveList) {
3460             /* Need to get game history */
3461             ics_getting_history = H_REQUESTED;
3462             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3463             SendToICS(str);
3464         }
3465         
3466         /* Initially flip the board to have black on the bottom if playing
3467            black or if the ICS flip flag is set, but let the user change
3468            it with the Flip View button. */
3469         flipView = appData.autoFlipView ? 
3470           (newGameMode == IcsPlayingBlack) || ics_flip :
3471           appData.flipView;
3472         
3473         /* Done with values from previous mode; copy in new ones */
3474         gameMode = newGameMode;
3475         ModeHighlight();
3476         ics_gamenum = gamenum;
3477         if (gamenum == gs_gamenum) {
3478             int klen = strlen(gs_kind);
3479             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3480             sprintf(str, "ICS %s", gs_kind);
3481             gameInfo.event = StrSave(str);
3482         } else {
3483             gameInfo.event = StrSave("ICS game");
3484         }
3485         gameInfo.site = StrSave(appData.icsHost);
3486         gameInfo.date = PGNDate();
3487         gameInfo.round = StrSave("-");
3488         gameInfo.white = StrSave(white);
3489         gameInfo.black = StrSave(black);
3490         timeControl = basetime * 60 * 1000;
3491         timeControl_2 = 0;
3492         timeIncrement = increment * 1000;
3493         movesPerSession = 0;
3494         gameInfo.timeControl = TimeControlTagValue();
3495         VariantSwitch(board, StringToVariant(gameInfo.event) );
3496   if (appData.debugMode) {
3497     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3498     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3499     setbuf(debugFP, NULL);
3500   }
3501
3502         gameInfo.outOfBook = NULL;
3503         
3504         /* Do we have the ratings? */
3505         if (strcmp(player1Name, white) == 0 &&
3506             strcmp(player2Name, black) == 0) {
3507             if (appData.debugMode)
3508               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3509                       player1Rating, player2Rating);
3510             gameInfo.whiteRating = player1Rating;
3511             gameInfo.blackRating = player2Rating;
3512         } else if (strcmp(player2Name, white) == 0 &&
3513                    strcmp(player1Name, black) == 0) {
3514             if (appData.debugMode)
3515               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3516                       player2Rating, player1Rating);
3517             gameInfo.whiteRating = player2Rating;
3518             gameInfo.blackRating = player1Rating;
3519         }
3520         player1Name[0] = player2Name[0] = NULLCHAR;
3521
3522         /* Silence shouts if requested */
3523         if (appData.quietPlay &&
3524             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3525             SendToICS(ics_prefix);
3526             SendToICS("set shout 0\n");
3527         }
3528     }
3529     
3530     /* Deal with midgame name changes */
3531     if (!newGame) {
3532         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3533             if (gameInfo.white) free(gameInfo.white);
3534             gameInfo.white = StrSave(white);
3535         }
3536         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3537             if (gameInfo.black) free(gameInfo.black);
3538             gameInfo.black = StrSave(black);
3539         }
3540     }
3541     
3542     /* Throw away game result if anything actually changes in examine mode */
3543     if (gameMode == IcsExamining && !newGame) {
3544         gameInfo.result = GameUnfinished;
3545         if (gameInfo.resultDetails != NULL) {
3546             free(gameInfo.resultDetails);
3547             gameInfo.resultDetails = NULL;
3548         }
3549     }
3550     
3551     /* In pausing && IcsExamining mode, we ignore boards coming
3552        in if they are in a different variation than we are. */
3553     if (pauseExamInvalid) return;
3554     if (pausing && gameMode == IcsExamining) {
3555         if (moveNum <= pauseExamForwardMostMove) {
3556             pauseExamInvalid = TRUE;
3557             forwardMostMove = pauseExamForwardMostMove;
3558             return;
3559         }
3560     }
3561     
3562   if (appData.debugMode) {
3563     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3564   }
3565     /* Parse the board */
3566     for (k = 0; k < ranks; k++) {
3567       for (j = 0; j < files; j++)
3568         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3569       if(gameInfo.holdingsWidth > 1) {
3570            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3571            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3572       }
3573     }
3574     CopyBoard(boards[moveNum], board);
3575     if (moveNum == 0) {
3576         startedFromSetupPosition =
3577           !CompareBoards(board, initialPosition);
3578         if(startedFromSetupPosition)
3579             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3580     }
3581
3582     /* [HGM] Set castling rights. Take the outermost Rooks,
3583        to make it also work for FRC opening positions. Note that board12
3584        is really defective for later FRC positions, as it has no way to
3585        indicate which Rook can castle if they are on the same side of King.
3586        For the initial position we grant rights to the outermost Rooks,
3587        and remember thos rights, and we then copy them on positions
3588        later in an FRC game. This means WB might not recognize castlings with
3589        Rooks that have moved back to their original position as illegal,
3590        but in ICS mode that is not its job anyway.
3591     */
3592     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3593     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3594
3595         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3596             if(board[0][i] == WhiteRook) j = i;
3597         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3599             if(board[0][i] == WhiteRook) j = i;
3600         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3601         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3602             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3603         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3604         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3605             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3606         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3607
3608         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3609         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3610             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3611         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3612             if(board[BOARD_HEIGHT-1][k] == bKing)
3613                 initialRights[5] = castlingRights[moveNum][5] = k;
3614     } else { int r;
3615         r = castlingRights[moveNum][0] = initialRights[0];
3616         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3617         r = castlingRights[moveNum][1] = initialRights[1];
3618         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3619         r = castlingRights[moveNum][3] = initialRights[3];
3620         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3621         r = castlingRights[moveNum][4] = initialRights[4];
3622         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3623         /* wildcastle kludge: always assume King has rights */
3624         r = castlingRights[moveNum][2] = initialRights[2];
3625         r = castlingRights[moveNum][5] = initialRights[5];
3626     }
3627     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3628     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3629
3630     
3631     if (ics_getting_history == H_GOT_REQ_HEADER ||
3632         ics_getting_history == H_GOT_UNREQ_HEADER) {
3633         /* This was an initial position from a move list, not
3634            the current position */
3635         return;
3636     }
3637     
3638     /* Update currentMove and known move number limits */
3639     newMove = newGame || moveNum > forwardMostMove;
3640
3641     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3642     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3643         takeback = forwardMostMove - moveNum;
3644         for (i = 0; i < takeback; i++) {
3645              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3646              SendToProgram("undo\n", &first);
3647         }
3648     }
3649
3650     if (newGame) {
3651         forwardMostMove = backwardMostMove = currentMove = moveNum;
3652         if (gameMode == IcsExamining && moveNum == 0) {
3653           /* Workaround for ICS limitation: we are not told the wild
3654              type when starting to examine a game.  But if we ask for
3655              the move list, the move list header will tell us */
3656             ics_getting_history = H_REQUESTED;
3657             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3658             SendToICS(str);
3659         }
3660     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3661                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3662         forwardMostMove = moveNum;
3663         if (!pausing || currentMove > forwardMostMove)
3664           currentMove = forwardMostMove;
3665     } else {
3666         /* New part of history that is not contiguous with old part */ 
3667         if (pausing && gameMode == IcsExamining) {
3668             pauseExamInvalid = TRUE;
3669             forwardMostMove = pauseExamForwardMostMove;
3670             return;
3671         }
3672         forwardMostMove = backwardMostMove = currentMove = moveNum;
3673         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3674             ics_getting_history = H_REQUESTED;
3675             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3676             SendToICS(str);
3677         }
3678     }
3679     
3680     /* Update the clocks */
3681     if (strchr(elapsed_time, '.')) {
3682       /* Time is in ms */
3683       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3684       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3685     } else {
3686       /* Time is in seconds */
3687       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3688       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3689     }
3690       
3691
3692 #if ZIPPY
3693     if (appData.zippyPlay && newGame &&
3694         gameMode != IcsObserving && gameMode != IcsIdle &&
3695         gameMode != IcsExamining)
3696       ZippyFirstBoard(moveNum, basetime, increment);
3697 #endif
3698     
3699     /* Put the move on the move list, first converting
3700        to canonical algebraic form. */
3701     if (moveNum > 0) {
3702   if (appData.debugMode) {
3703     if (appData.debugMode) { int f = forwardMostMove;
3704         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3705                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3706     }
3707     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3708     fprintf(debugFP, "moveNum = %d\n", moveNum);
3709     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3710     setbuf(debugFP, NULL);
3711   }
3712         if (moveNum <= backwardMostMove) {
3713             /* We don't know what the board looked like before
3714                this move.  Punt. */
3715             strcpy(parseList[moveNum - 1], move_str);
3716             strcat(parseList[moveNum - 1], " ");
3717             strcat(parseList[moveNum - 1], elapsed_time);
3718             moveList[moveNum - 1][0] = NULLCHAR;
3719         } else if (strcmp(move_str, "none") == 0) {
3720             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3721             /* Again, we don't know what the board looked like;
3722                this is really the start of the game. */
3723             parseList[moveNum - 1][0] = NULLCHAR;
3724             moveList[moveNum - 1][0] = NULLCHAR;
3725             backwardMostMove = moveNum;
3726             startedFromSetupPosition = TRUE;
3727             fromX = fromY = toX = toY = -1;
3728         } else {
3729           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3730           //                 So we parse the long-algebraic move string in stead of the SAN move
3731           int valid; char buf[MSG_SIZ], *prom;
3732
3733           // str looks something like "Q/a1-a2"; kill the slash
3734           if(str[1] == '/') 
3735                 sprintf(buf, "%c%s", str[0], str+2);
3736           else  strcpy(buf, str); // might be castling
3737           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3738                 strcat(buf, prom); // long move lacks promo specification!
3739           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3740                 if(appData.debugMode) 
3741                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3742                 strcpy(move_str, buf);
3743           }
3744           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3745                                 &fromX, &fromY, &toX, &toY, &promoChar)
3746                || ParseOneMove(buf, moveNum - 1, &moveType,
3747                                 &fromX, &fromY, &toX, &toY, &promoChar);
3748           // end of long SAN patch
3749           if (valid) {
3750             (void) CoordsToAlgebraic(boards[moveNum - 1],
3751                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3752                                      fromY, fromX, toY, toX, promoChar,
3753                                      parseList[moveNum-1]);
3754             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3755                              castlingRights[moveNum]) ) {
3756               case MT_NONE:
3757               case MT_STALEMATE:
3758               default:
3759                 break;
3760               case MT_CHECK:
3761                 if(gameInfo.variant != VariantShogi)
3762                     strcat(parseList[moveNum - 1], "+");
3763                 break;
3764               case MT_CHECKMATE:
3765               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3766                 strcat(parseList[moveNum - 1], "#");
3767                 break;
3768             }
3769             strcat(parseList[moveNum - 1], " ");
3770             strcat(parseList[moveNum - 1], elapsed_time);
3771             /* currentMoveString is set as a side-effect of ParseOneMove */
3772             strcpy(moveList[moveNum - 1], currentMoveString);
3773             strcat(moveList[moveNum - 1], "\n");
3774           } else {
3775             /* Move from ICS was illegal!?  Punt. */
3776   if (appData.debugMode) {
3777     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3778     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3779   }
3780             strcpy(parseList[moveNum - 1], move_str);
3781             strcat(parseList[moveNum - 1], " ");
3782             strcat(parseList[moveNum - 1], elapsed_time);
3783             moveList[moveNum - 1][0] = NULLCHAR;
3784             fromX = fromY = toX = toY = -1;
3785           }
3786         }
3787   if (appData.debugMode) {
3788     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3789     setbuf(debugFP, NULL);
3790   }
3791
3792 #if ZIPPY
3793         /* Send move to chess program (BEFORE animating it). */
3794         if (appData.zippyPlay && !newGame && newMove && 
3795            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3796
3797             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3798                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3799                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3800                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3801                             move_str);
3802                     DisplayError(str, 0);
3803                 } else {
3804                     if (first.sendTime) {
3805                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3806                     }
3807                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3808                     if (firstMove && !bookHit) {
3809                         firstMove = FALSE;
3810                         if (first.useColors) {
3811                           SendToProgram(gameMode == IcsPlayingWhite ?
3812                                         "white\ngo\n" :
3813                                         "black\ngo\n", &first);
3814                         } else {
3815                           SendToProgram("go\n", &first);
3816                         }
3817                         first.maybeThinking = TRUE;
3818                     }
3819                 }
3820             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3821               if (moveList[moveNum - 1][0] == NULLCHAR) {
3822                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3823                 DisplayError(str, 0);
3824               } else {
3825                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3826                 SendMoveToProgram(moveNum - 1, &first);
3827               }
3828             }
3829         }
3830 #endif
3831     }
3832
3833     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3834         /* If move comes from a remote source, animate it.  If it
3835            isn't remote, it will have already been animated. */
3836         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3837             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3838         }
3839         if (!pausing && appData.highlightLastMove) {
3840             SetHighlights(fromX, fromY, toX, toY);
3841         }
3842     }
3843     
3844     /* Start the clocks */
3845     whiteFlag = blackFlag = FALSE;
3846     appData.clockMode = !(basetime == 0 && increment == 0);
3847     if (ticking == 0) {
3848       ics_clock_paused = TRUE;
3849       StopClocks();
3850     } else if (ticking == 1) {
3851       ics_clock_paused = FALSE;
3852     }
3853     if (gameMode == IcsIdle ||
3854         relation == RELATION_OBSERVING_STATIC ||
3855         relation == RELATION_EXAMINING ||
3856         ics_clock_paused)
3857       DisplayBothClocks();
3858     else
3859       StartClocks();
3860     
3861     /* Display opponents and material strengths */
3862     if (gameInfo.variant != VariantBughouse &&
3863         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3864         if (tinyLayout || smallLayout) {
3865             if(gameInfo.variant == VariantNormal)
3866                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3867                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3868                     basetime, increment);
3869             else
3870                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3871                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3872                     basetime, increment, (int) gameInfo.variant);
3873         } else {
3874             if(gameInfo.variant == VariantNormal)
3875                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3876                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3877                     basetime, increment);
3878             else
3879                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3880                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3881                     basetime, increment, VariantName(gameInfo.variant));
3882         }
3883         DisplayTitle(str);
3884   if (appData.debugMode) {
3885     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3886   }
3887     }
3888
3889    
3890     /* Display the board */
3891     if (!pausing && !appData.noGUI) {
3892       
3893       if (appData.premove)
3894           if (!gotPremove || 
3895              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3896              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3897               ClearPremoveHighlights();
3898
3899       DrawPosition(FALSE, boards[currentMove]);
3900       DisplayMove(moveNum - 1);
3901       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3902             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3903               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3904         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3905       }
3906     }
3907
3908     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3909 #if ZIPPY
3910     if(bookHit) { // [HGM] book: simulate book reply
3911         static char bookMove[MSG_SIZ]; // a bit generous?
3912
3913         programStats.nodes = programStats.depth = programStats.time = 
3914         programStats.score = programStats.got_only_move = 0;
3915         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3916
3917         strcpy(bookMove, "move ");
3918         strcat(bookMove, bookHit);
3919         HandleMachineMove(bookMove, &first);
3920     }
3921 #endif
3922 }
3923
3924 void
3925 GetMoveListEvent()
3926 {
3927     char buf[MSG_SIZ];
3928     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3929         ics_getting_history = H_REQUESTED;
3930         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3931         SendToICS(buf);
3932     }
3933 }
3934
3935 void
3936 AnalysisPeriodicEvent(force)
3937      int force;
3938 {
3939     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3940          && !force) || !appData.periodicUpdates)
3941       return;
3942
3943     /* Send . command to Crafty to collect stats */
3944     SendToProgram(".\n", &first);
3945
3946     /* Don't send another until we get a response (this makes
3947        us stop sending to old Crafty's which don't understand
3948        the "." command (sending illegal cmds resets node count & time,
3949        which looks bad)) */
3950     programStats.ok_to_send = 0;
3951 }
3952
3953 void ics_update_width(new_width)
3954         int new_width;
3955 {
3956         ics_printf("set width %d\n", new_width);
3957 }
3958
3959 void
3960 SendMoveToProgram(moveNum, cps)
3961      int moveNum;
3962      ChessProgramState *cps;
3963 {
3964     char buf[MSG_SIZ];
3965
3966     if (cps->useUsermove) {
3967       SendToProgram("usermove ", cps);
3968     }
3969     if (cps->useSAN) {
3970       char *space;
3971       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3972         int len = space - parseList[moveNum];
3973         memcpy(buf, parseList[moveNum], len);
3974         buf[len++] = '\n';
3975         buf[len] = NULLCHAR;
3976       } else {
3977         sprintf(buf, "%s\n", parseList[moveNum]);
3978       }
3979       SendToProgram(buf, cps);
3980     } else {
3981       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3982         AlphaRank(moveList[moveNum], 4);
3983         SendToProgram(moveList[moveNum], cps);
3984         AlphaRank(moveList[moveNum], 4); // and back
3985       } else
3986       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3987        * the engine. It would be nice to have a better way to identify castle 
3988        * moves here. */
3989       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3990                                                                          && cps->useOOCastle) {
3991         int fromX = moveList[moveNum][0] - AAA; 
3992         int fromY = moveList[moveNum][1] - ONE;
3993         int toX = moveList[moveNum][2] - AAA; 
3994         int toY = moveList[moveNum][3] - ONE;
3995         if((boards[moveNum][fromY][fromX] == WhiteKing 
3996             && boards[moveNum][toY][toX] == WhiteRook)
3997            || (boards[moveNum][fromY][fromX] == BlackKing 
3998                && boards[moveNum][toY][toX] == BlackRook)) {
3999           if(toX > fromX) SendToProgram("O-O\n", cps);
4000           else SendToProgram("O-O-O\n", cps);
4001         }
4002         else SendToProgram(moveList[moveNum], cps);
4003       }
4004       else SendToProgram(moveList[moveNum], cps);
4005       /* End of additions by Tord */
4006     }
4007
4008     /* [HGM] setting up the opening has brought engine in force mode! */
4009     /*       Send 'go' if we are in a mode where machine should play. */
4010     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4011         (gameMode == TwoMachinesPlay   ||
4012 #ifdef ZIPPY
4013          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4014 #endif
4015          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4016         SendToProgram("go\n", cps);
4017   if (appData.debugMode) {
4018     fprintf(debugFP, "(extra)\n");
4019   }
4020     }
4021     setboardSpoiledMachineBlack = 0;
4022 }
4023
4024 void
4025 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4026      ChessMove moveType;
4027      int fromX, fromY, toX, toY;
4028 {
4029     char user_move[MSG_SIZ];
4030
4031     switch (moveType) {
4032       default:
4033         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4034                 (int)moveType, fromX, fromY, toX, toY);
4035         DisplayError(user_move + strlen("say "), 0);
4036         break;
4037       case WhiteKingSideCastle:
4038       case BlackKingSideCastle:
4039       case WhiteQueenSideCastleWild:
4040       case BlackQueenSideCastleWild:
4041       /* PUSH Fabien */
4042       case WhiteHSideCastleFR:
4043       case BlackHSideCastleFR:
4044       /* POP Fabien */
4045         sprintf(user_move, "o-o\n");
4046         break;
4047       case WhiteQueenSideCastle:
4048       case BlackQueenSideCastle:
4049       case WhiteKingSideCastleWild:
4050       case BlackKingSideCastleWild:
4051       /* PUSH Fabien */
4052       case WhiteASideCastleFR:
4053       case BlackASideCastleFR:
4054       /* POP Fabien */
4055         sprintf(user_move, "o-o-o\n");
4056         break;
4057       case WhitePromotionQueen:
4058       case BlackPromotionQueen:
4059       case WhitePromotionRook:
4060       case BlackPromotionRook:
4061       case WhitePromotionBishop:
4062       case BlackPromotionBishop:
4063       case WhitePromotionKnight:
4064       case BlackPromotionKnight:
4065       case WhitePromotionKing:
4066       case BlackPromotionKing:
4067       case WhitePromotionChancellor:
4068       case BlackPromotionChancellor:
4069       case WhitePromotionArchbishop:
4070       case BlackPromotionArchbishop:
4071         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4072             sprintf(user_move, "%c%c%c%c=%c\n",
4073                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4074                 PieceToChar(WhiteFerz));
4075         else if(gameInfo.variant == VariantGreat)
4076             sprintf(user_move, "%c%c%c%c=%c\n",
4077                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4078                 PieceToChar(WhiteMan));
4079         else
4080             sprintf(user_move, "%c%c%c%c=%c\n",
4081                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4082                 PieceToChar(PromoPiece(moveType)));
4083         break;
4084       case WhiteDrop:
4085       case BlackDrop:
4086         sprintf(user_move, "%c@%c%c\n",
4087                 ToUpper(PieceToChar((ChessSquare) fromX)),
4088                 AAA + toX, ONE + toY);
4089         break;
4090       case NormalMove:
4091       case WhiteCapturesEnPassant:
4092       case BlackCapturesEnPassant:
4093       case IllegalMove:  /* could be a variant we don't quite understand */
4094         sprintf(user_move, "%c%c%c%c\n",
4095                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4096         break;
4097     }
4098     SendToICS(user_move);
4099     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4100         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4101 }
4102
4103 void
4104 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4105      int rf, ff, rt, ft;
4106      char promoChar;
4107      char move[7];
4108 {
4109     if (rf == DROP_RANK) {
4110         sprintf(move, "%c@%c%c\n",
4111                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4112     } else {
4113         if (promoChar == 'x' || promoChar == NULLCHAR) {
4114             sprintf(move, "%c%c%c%c\n",
4115                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4116         } else {
4117             sprintf(move, "%c%c%c%c%c\n",
4118                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4119         }
4120     }
4121 }
4122
4123 void
4124 ProcessICSInitScript(f)
4125      FILE *f;
4126 {
4127     char buf[MSG_SIZ];
4128
4129     while (fgets(buf, MSG_SIZ, f)) {
4130         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4131     }
4132
4133     fclose(f);
4134 }
4135
4136
4137 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4138 void
4139 AlphaRank(char *move, int n)
4140 {
4141 //    char *p = move, c; int x, y;
4142
4143     if (appData.debugMode) {
4144         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4145     }
4146
4147     if(move[1]=='*' && 
4148        move[2]>='0' && move[2]<='9' &&
4149        move[3]>='a' && move[3]<='x'    ) {
4150         move[1] = '@';
4151         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4152         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4153     } else
4154     if(move[0]>='0' && move[0]<='9' &&
4155        move[1]>='a' && move[1]<='x' &&
4156        move[2]>='0' && move[2]<='9' &&
4157        move[3]>='a' && move[3]<='x'    ) {
4158         /* input move, Shogi -> normal */
4159         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4160         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4161         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4162         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4163     } else
4164     if(move[1]=='@' &&
4165        move[3]>='0' && move[3]<='9' &&
4166        move[2]>='a' && move[2]<='x'    ) {
4167         move[1] = '*';
4168         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4169         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4170     } else
4171     if(
4172        move[0]>='a' && move[0]<='x' &&
4173        move[3]>='0' && move[3]<='9' &&
4174        move[2]>='a' && move[2]<='x'    ) {
4175          /* output move, normal -> Shogi */
4176         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4177         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4178         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4179         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4180         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4181     }
4182     if (appData.debugMode) {
4183         fprintf(debugFP, "   out = '%s'\n", move);
4184     }
4185 }
4186
4187 /* Parser for moves from gnuchess, ICS, or user typein box */
4188 Boolean
4189 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4190      char *move;
4191      int moveNum;
4192      ChessMove *moveType;
4193      int *fromX, *fromY, *toX, *toY;
4194      char *promoChar;
4195 {       
4196     if (appData.debugMode) {
4197         fprintf(debugFP, "move to parse: %s\n", move);
4198     }
4199     *moveType = yylexstr(moveNum, move);
4200
4201     switch (*moveType) {
4202       case WhitePromotionChancellor:
4203       case BlackPromotionChancellor:
4204       case WhitePromotionArchbishop:
4205       case BlackPromotionArchbishop:
4206       case WhitePromotionQueen:
4207       case BlackPromotionQueen:
4208       case WhitePromotionRook:
4209       case BlackPromotionRook:
4210       case WhitePromotionBishop:
4211       case BlackPromotionBishop:
4212       case WhitePromotionKnight:
4213       case BlackPromotionKnight:
4214       case WhitePromotionKing:
4215       case BlackPromotionKing:
4216       case NormalMove:
4217       case WhiteCapturesEnPassant:
4218       case BlackCapturesEnPassant:
4219       case WhiteKingSideCastle:
4220       case WhiteQueenSideCastle:
4221       case BlackKingSideCastle:
4222       case BlackQueenSideCastle:
4223       case WhiteKingSideCastleWild:
4224       case WhiteQueenSideCastleWild:
4225       case BlackKingSideCastleWild:
4226       case BlackQueenSideCastleWild:
4227       /* Code added by Tord: */
4228       case WhiteHSideCastleFR:
4229       case WhiteASideCastleFR:
4230       case BlackHSideCastleFR:
4231       case BlackASideCastleFR:
4232       /* End of code added by Tord */
4233       case IllegalMove:         /* bug or odd chess variant */
4234         *fromX = currentMoveString[0] - AAA;
4235         *fromY = currentMoveString[1] - ONE;
4236         *toX = currentMoveString[2] - AAA;
4237         *toY = currentMoveString[3] - ONE;
4238         *promoChar = currentMoveString[4];
4239         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4240             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4241     if (appData.debugMode) {
4242         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4243     }
4244             *fromX = *fromY = *toX = *toY = 0;
4245             return FALSE;
4246         }
4247         if (appData.testLegality) {
4248           return (*moveType != IllegalMove);
4249         } else {
4250           return !(fromX == fromY && toX == toY);
4251         }
4252
4253       case WhiteDrop:
4254       case BlackDrop:
4255         *fromX = *moveType == WhiteDrop ?
4256           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4257           (int) CharToPiece(ToLower(currentMoveString[0]));
4258         *fromY = DROP_RANK;
4259         *toX = currentMoveString[2] - AAA;
4260         *toY = currentMoveString[3] - ONE;
4261         *promoChar = NULLCHAR;
4262         return TRUE;
4263
4264       case AmbiguousMove:
4265       case ImpossibleMove:
4266       case (ChessMove) 0:       /* end of file */
4267       case ElapsedTime:
4268       case Comment:
4269       case PGNTag:
4270       case NAG:
4271       case WhiteWins:
4272       case BlackWins:
4273       case GameIsDrawn:
4274       default:
4275     if (appData.debugMode) {
4276         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4277     }
4278         /* bug? */
4279         *fromX = *fromY = *toX = *toY = 0;
4280         *promoChar = NULLCHAR;
4281         return FALSE;
4282     }
4283 }
4284
4285 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4286 // All positions will have equal probability, but the current method will not provide a unique
4287 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4288 #define DARK 1
4289 #define LITE 2
4290 #define ANY 3
4291
4292 int squaresLeft[4];
4293 int piecesLeft[(int)BlackPawn];
4294 int seed, nrOfShuffles;
4295
4296 void GetPositionNumber()
4297 {       // sets global variable seed
4298         int i;
4299
4300         seed = appData.defaultFrcPosition;
4301         if(seed < 0) { // randomize based on time for negative FRC position numbers
4302                 for(i=0; i<50; i++) seed += random();
4303                 seed = random() ^ random() >> 8 ^ random() << 8;
4304                 if(seed<0) seed = -seed;
4305         }
4306 }
4307
4308 int put(Board board, int pieceType, int rank, int n, int shade)
4309 // put the piece on the (n-1)-th empty squares of the given shade
4310 {
4311         int i;
4312
4313         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4314                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4315                         board[rank][i] = (ChessSquare) pieceType;
4316                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4317                         squaresLeft[ANY]--;
4318                         piecesLeft[pieceType]--; 
4319                         return i;
4320                 }
4321         }
4322         return -1;
4323 }
4324
4325
4326 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4327 // calculate where the next piece goes, (any empty square), and put it there
4328 {
4329         int i;
4330
4331         i = seed % squaresLeft[shade];
4332         nrOfShuffles *= squaresLeft[shade];
4333         seed /= squaresLeft[shade];
4334         put(board, pieceType, rank, i, shade);
4335 }
4336
4337 void AddTwoPieces(Board board, int pieceType, int rank)
4338 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4339 {
4340         int i, n=squaresLeft[ANY], j=n-1, k;
4341
4342         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4343         i = seed % k;  // pick one
4344         nrOfShuffles *= k;
4345         seed /= k;
4346         while(i >= j) i -= j--;
4347         j = n - 1 - j; i += j;
4348         put(board, pieceType, rank, j, ANY);
4349         put(board, pieceType, rank, i, ANY);
4350 }
4351
4352 void SetUpShuffle(Board board, int number)
4353 {
4354         int i, p, first=1;
4355
4356         GetPositionNumber(); nrOfShuffles = 1;
4357
4358         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4359         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4360         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4361
4362         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4363
4364         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4365             p = (int) board[0][i];
4366             if(p < (int) BlackPawn) piecesLeft[p] ++;
4367             board[0][i] = EmptySquare;
4368         }
4369
4370         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4371             // shuffles restricted to allow normal castling put KRR first
4372             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4373                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4374             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4375                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4376             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4377                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4378             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4379                 put(board, WhiteRook, 0, 0, ANY);
4380             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4381         }
4382
4383         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4384             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4385             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4386                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4387                 while(piecesLeft[p] >= 2) {
4388                     AddOnePiece(board, p, 0, LITE);
4389                     AddOnePiece(board, p, 0, DARK);
4390                 }
4391                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4392             }
4393
4394         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4395             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4396             // but we leave King and Rooks for last, to possibly obey FRC restriction
4397             if(p == (int)WhiteRook) continue;
4398             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4399             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4400         }
4401
4402         // now everything is placed, except perhaps King (Unicorn) and Rooks
4403
4404         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4405             // Last King gets castling rights
4406             while(piecesLeft[(int)WhiteUnicorn]) {
4407                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4408                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4409             }
4410
4411             while(piecesLeft[(int)WhiteKing]) {
4412                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4413                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4414             }
4415
4416
4417         } else {
4418             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4419             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4420         }
4421
4422         // Only Rooks can be left; simply place them all
4423         while(piecesLeft[(int)WhiteRook]) {
4424                 i = put(board, WhiteRook, 0, 0, ANY);
4425                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4426                         if(first) {
4427                                 first=0;
4428                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4429                         }
4430                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4431                 }
4432         }
4433         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4434             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4435         }
4436
4437         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4438 }
4439
4440 int SetCharTable( char *table, const char * map )
4441 /* [HGM] moved here from winboard.c because of its general usefulness */
4442 /*       Basically a safe strcpy that uses the last character as King */
4443 {
4444     int result = FALSE; int NrPieces;
4445
4446     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4447                     && NrPieces >= 12 && !(NrPieces&1)) {
4448         int i; /* [HGM] Accept even length from 12 to 34 */
4449
4450         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4451         for( i=0; i<NrPieces/2-1; i++ ) {
4452             table[i] = map[i];
4453             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4454         }
4455         table[(int) WhiteKing]  = map[NrPieces/2-1];
4456         table[(int) BlackKing]  = map[NrPieces-1];
4457
4458         result = TRUE;
4459     }
4460
4461     return result;
4462 }
4463
4464 void Prelude(Board board)
4465 {       // [HGM] superchess: random selection of exo-pieces
4466         int i, j, k; ChessSquare p; 
4467         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4468
4469         GetPositionNumber(); // use FRC position number
4470
4471         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4472             SetCharTable(pieceToChar, appData.pieceToCharTable);
4473             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4474                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4475         }
4476
4477         j = seed%4;                 seed /= 4; 
4478         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4479         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4480         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4481         j = seed%3 + (seed%3 >= j); seed /= 3; 
4482         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4483         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4484         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4485         j = seed%3;                 seed /= 3; 
4486         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4487         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4488         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4489         j = seed%2 + (seed%2 >= j); seed /= 2; 
4490         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4491         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4492         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4493         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4494         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4495         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4496         put(board, exoPieces[0],    0, 0, ANY);
4497         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4498 }
4499
4500 void
4501 InitPosition(redraw)
4502      int redraw;
4503 {
4504     ChessSquare (* pieces)[BOARD_SIZE];
4505     int i, j, pawnRow, overrule,
4506     oldx = gameInfo.boardWidth,
4507     oldy = gameInfo.boardHeight,
4508     oldh = gameInfo.holdingsWidth,
4509     oldv = gameInfo.variant;
4510
4511     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4512
4513     /* [AS] Initialize pv info list [HGM] and game status */
4514     {
4515         for( i=0; i<MAX_MOVES; i++ ) {
4516             pvInfoList[i].depth = 0;
4517             epStatus[i]=EP_NONE;
4518             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4519         }
4520
4521         initialRulePlies = 0; /* 50-move counter start */
4522
4523         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4524         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4525     }
4526
4527     
4528     /* [HGM] logic here is completely changed. In stead of full positions */
4529     /* the initialized data only consist of the two backranks. The switch */
4530     /* selects which one we will use, which is than copied to the Board   */
4531     /* initialPosition, which for the rest is initialized by Pawns and    */
4532     /* empty squares. This initial position is then copied to boards[0],  */
4533     /* possibly after shuffling, so that it remains available.            */
4534
4535     gameInfo.holdingsWidth = 0; /* default board sizes */
4536     gameInfo.boardWidth    = 8;
4537     gameInfo.boardHeight   = 8;
4538     gameInfo.holdingsSize  = 0;
4539     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4540     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4541     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4542
4543     switch (gameInfo.variant) {
4544     case VariantFischeRandom:
4545       shuffleOpenings = TRUE;
4546     default:
4547       pieces = FIDEArray;
4548       break;
4549     case VariantShatranj:
4550       pieces = ShatranjArray;
4551       nrCastlingRights = 0;
4552       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4553       break;
4554     case VariantTwoKings:
4555       pieces = twoKingsArray;
4556       break;
4557     case VariantCapaRandom:
4558       shuffleOpenings = TRUE;
4559     case VariantCapablanca:
4560       pieces = CapablancaArray;
4561       gameInfo.boardWidth = 10;
4562       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4563       break;
4564     case VariantGothic:
4565       pieces = GothicArray;
4566       gameInfo.boardWidth = 10;
4567       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4568       break;
4569     case VariantJanus:
4570       pieces = JanusArray;
4571       gameInfo.boardWidth = 10;
4572       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4573       nrCastlingRights = 6;
4574         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4575         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4576         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4577         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4578         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4579         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4580       break;
4581     case VariantFalcon:
4582       pieces = FalconArray;
4583       gameInfo.boardWidth = 10;
4584       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4585       break;
4586     case VariantXiangqi:
4587       pieces = XiangqiArray;
4588       gameInfo.boardWidth  = 9;
4589       gameInfo.boardHeight = 10;
4590       nrCastlingRights = 0;
4591       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4592       break;
4593     case VariantShogi:
4594       pieces = ShogiArray;
4595       gameInfo.boardWidth  = 9;
4596       gameInfo.boardHeight = 9;
4597       gameInfo.holdingsSize = 7;
4598       nrCastlingRights = 0;
4599       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4600       break;
4601     case VariantCourier:
4602       pieces = CourierArray;
4603       gameInfo.boardWidth  = 12;
4604       nrCastlingRights = 0;
4605       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4606       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4607       break;
4608     case VariantKnightmate:
4609       pieces = KnightmateArray;
4610       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4611       break;
4612     case VariantFairy:
4613       pieces = fairyArray;
4614       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4615       break;
4616     case VariantGreat:
4617       pieces = GreatArray;
4618       gameInfo.boardWidth = 10;
4619       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4620       gameInfo.holdingsSize = 8;
4621       break;
4622     case VariantSuper:
4623       pieces = FIDEArray;
4624       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4625       gameInfo.holdingsSize = 8;
4626       startedFromSetupPosition = TRUE;
4627       break;
4628     case VariantCrazyhouse:
4629     case VariantBughouse:
4630       pieces = FIDEArray;
4631       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4632       gameInfo.holdingsSize = 5;
4633       break;
4634     case VariantWildCastle:
4635       pieces = FIDEArray;
4636       /* !!?shuffle with kings guaranteed to be on d or e file */
4637       shuffleOpenings = 1;
4638       break;
4639     case VariantNoCastle:
4640       pieces = FIDEArray;
4641       nrCastlingRights = 0;
4642       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4643       /* !!?unconstrained back-rank shuffle */
4644       shuffleOpenings = 1;
4645       break;
4646     }
4647
4648     overrule = 0;
4649     if(appData.NrFiles >= 0) {
4650         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4651         gameInfo.boardWidth = appData.NrFiles;
4652     }
4653     if(appData.NrRanks >= 0) {
4654         gameInfo.boardHeight = appData.NrRanks;
4655     }
4656     if(appData.holdingsSize >= 0) {
4657         i = appData.holdingsSize;
4658         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4659         gameInfo.holdingsSize = i;
4660     }
4661     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4662     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4663         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4664
4665     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4666     if(pawnRow < 1) pawnRow = 1;
4667
4668     /* User pieceToChar list overrules defaults */
4669     if(appData.pieceToCharTable != NULL)
4670         SetCharTable(pieceToChar, appData.pieceToCharTable);
4671
4672     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4673
4674         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4675             s = (ChessSquare) 0; /* account holding counts in guard band */
4676         for( i=0; i<BOARD_HEIGHT; i++ )
4677             initialPosition[i][j] = s;
4678
4679         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4680         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4681         initialPosition[pawnRow][j] = WhitePawn;
4682         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4683         if(gameInfo.variant == VariantXiangqi) {
4684             if(j&1) {
4685                 initialPosition[pawnRow][j] = 
4686                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4687                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4688                    initialPosition[2][j] = WhiteCannon;
4689                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4690                 }
4691             }
4692         }
4693         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4694     }
4695     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4696
4697             j=BOARD_LEFT+1;
4698             initialPosition[1][j] = WhiteBishop;
4699             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4700             j=BOARD_RGHT-2;
4701             initialPosition[1][j] = WhiteRook;
4702             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4703     }
4704
4705     if( nrCastlingRights == -1) {
4706         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4707         /*       This sets default castling rights from none to normal corners   */
4708         /* Variants with other castling rights must set them themselves above    */
4709         nrCastlingRights = 6;
4710        
4711         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4712         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4713         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4714         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4715         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4716         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4717      }
4718
4719      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4720      if(gameInfo.variant == VariantGreat) { // promotion commoners
4721         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4722         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4723         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4724         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4725      }
4726   if (appData.debugMode) {
4727     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4728   }
4729     if(shuffleOpenings) {
4730         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4731         startedFromSetupPosition = TRUE;
4732     }
4733     if(startedFromPositionFile) {
4734       /* [HGM] loadPos: use PositionFile for every new game */
4735       CopyBoard(initialPosition, filePosition);
4736       for(i=0; i<nrCastlingRights; i++)
4737           castlingRights[0][i] = initialRights[i] = fileRights[i];
4738       startedFromSetupPosition = TRUE;
4739     }
4740
4741     CopyBoard(boards[0], initialPosition);
4742
4743     if(oldx != gameInfo.boardWidth ||
4744        oldy != gameInfo.boardHeight ||
4745        oldh != gameInfo.holdingsWidth
4746 #ifdef GOTHIC
4747        || oldv == VariantGothic ||        // For licensing popups
4748        gameInfo.variant == VariantGothic
4749 #endif
4750 #ifdef FALCON
4751        || oldv == VariantFalcon ||
4752        gameInfo.variant == VariantFalcon
4753 #endif
4754                                          )
4755             InitDrawingSizes(-2 ,0);
4756
4757     if (redraw)
4758       DrawPosition(TRUE, boards[currentMove]);
4759 }
4760
4761 void
4762 SendBoard(cps, moveNum)
4763      ChessProgramState *cps;
4764      int moveNum;
4765 {
4766     char message[MSG_SIZ];
4767     
4768     if (cps->useSetboard) {
4769       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4770       sprintf(message, "setboard %s\n", fen);
4771       SendToProgram(message, cps);
4772       free(fen);
4773
4774     } else {
4775       ChessSquare *bp;
4776       int i, j;
4777       /* Kludge to set black to move, avoiding the troublesome and now
4778        * deprecated "black" command.
4779        */
4780       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4781
4782       SendToProgram("edit\n", cps);
4783       SendToProgram("#\n", cps);
4784       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4785         bp = &boards[moveNum][i][BOARD_LEFT];
4786         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4787           if ((int) *bp < (int) BlackPawn) {
4788             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4789                     AAA + j, ONE + i);
4790             if(message[0] == '+' || message[0] == '~') {
4791                 sprintf(message, "%c%c%c+\n",
4792                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4793                         AAA + j, ONE + i);
4794             }
4795             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4796                 message[1] = BOARD_RGHT   - 1 - j + '1';
4797                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4798             }
4799             SendToProgram(message, cps);
4800           }
4801         }
4802       }
4803     
4804       SendToProgram("c\n", cps);
4805       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4806         bp = &boards[moveNum][i][BOARD_LEFT];
4807         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4808           if (((int) *bp != (int) EmptySquare)
4809               && ((int) *bp >= (int) BlackPawn)) {
4810             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4811                     AAA + j, ONE + i);
4812             if(message[0] == '+' || message[0] == '~') {
4813                 sprintf(message, "%c%c%c+\n",
4814                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4815                         AAA + j, ONE + i);
4816             }
4817             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4818                 message[1] = BOARD_RGHT   - 1 - j + '1';
4819                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4820             }
4821             SendToProgram(message, cps);
4822           }
4823         }
4824       }
4825     
4826       SendToProgram(".\n", cps);
4827     }
4828     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4829 }
4830
4831 int
4832 IsPromotion(fromX, fromY, toX, toY)
4833      int fromX, fromY, toX, toY;
4834 {
4835     /* [HGM] add Shogi promotions */
4836     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4837     ChessSquare piece;
4838
4839     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4840       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4841    /* [HGM] Note to self: line above also weeds out drops */
4842     piece = boards[currentMove][fromY][fromX];
4843     if(gameInfo.variant == VariantShogi) {
4844         promotionZoneSize = 3;
4845         highestPromotingPiece = (int)WhiteKing;
4846         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4847            and if in normal chess we then allow promotion to King, why not
4848            allow promotion of other piece in Shogi?                         */
4849     }
4850     if((int)piece >= BlackPawn) {
4851         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4852              return FALSE;
4853         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4854     } else {
4855         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4856            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4857     }
4858     return ( (int)piece <= highestPromotingPiece );
4859 }
4860
4861 int
4862 InPalace(row, column)
4863      int row, column;
4864 {   /* [HGM] for Xiangqi */
4865     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4866          column < (BOARD_WIDTH + 4)/2 &&
4867          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4868     return FALSE;
4869 }
4870
4871 int
4872 PieceForSquare (x, y)
4873      int x;
4874      int y;
4875 {
4876   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4877      return -1;
4878   else
4879      return boards[currentMove][y][x];
4880 }
4881
4882 int
4883 OKToStartUserMove(x, y)
4884      int x, y;
4885 {
4886     ChessSquare from_piece;
4887     int white_piece;
4888
4889     if (matchMode) return FALSE;
4890     if (gameMode == EditPosition) return TRUE;
4891
4892     if (x >= 0 && y >= 0)
4893       from_piece = boards[currentMove][y][x];
4894     else
4895       from_piece = EmptySquare;
4896
4897     if (from_piece == EmptySquare) return FALSE;
4898
4899     white_piece = (int)from_piece >= (int)WhitePawn &&
4900       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4901
4902     switch (gameMode) {
4903       case PlayFromGameFile:
4904       case AnalyzeFile:
4905       case TwoMachinesPlay:
4906       case EndOfGame:
4907         return FALSE;
4908
4909       case IcsObserving:
4910       case IcsIdle:
4911         return FALSE;
4912
4913       case MachinePlaysWhite:
4914       case IcsPlayingBlack:
4915         if (appData.zippyPlay) return FALSE;
4916         if (white_piece) {
4917             DisplayMoveError(_("You are playing Black"));
4918             return FALSE;
4919         }
4920         break;
4921
4922       case MachinePlaysBlack:
4923       case IcsPlayingWhite:
4924         if (appData.zippyPlay) return FALSE;
4925         if (!white_piece) {
4926             DisplayMoveError(_("You are playing White"));
4927             return FALSE;
4928         }
4929         break;
4930
4931       case EditGame:
4932         if (!white_piece && WhiteOnMove(currentMove)) {
4933             DisplayMoveError(_("It is White's turn"));
4934             return FALSE;
4935         }           
4936         if (white_piece && !WhiteOnMove(currentMove)) {
4937             DisplayMoveError(_("It is Black's turn"));
4938             return FALSE;
4939         }           
4940         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4941             /* Editing correspondence game history */
4942             /* Could disallow this or prompt for confirmation */
4943             cmailOldMove = -1;
4944         }
4945         if (currentMove < forwardMostMove) {
4946             /* Discarding moves */
4947             /* Could prompt for confirmation here,
4948                but I don't think that's such a good idea */
4949             forwardMostMove = currentMove;
4950         }
4951         break;
4952
4953       case BeginningOfGame:
4954         if (appData.icsActive) return FALSE;
4955         if (!appData.noChessProgram) {
4956             if (!white_piece) {
4957                 DisplayMoveError(_("You are playing White"));
4958                 return FALSE;
4959             }
4960         }
4961         break;
4962         
4963       case Training:
4964         if (!white_piece && WhiteOnMove(currentMove)) {
4965             DisplayMoveError(_("It is White's turn"));
4966             return FALSE;
4967         }           
4968         if (white_piece && !WhiteOnMove(currentMove)) {
4969             DisplayMoveError(_("It is Black's turn"));
4970             return FALSE;
4971         }           
4972         break;
4973
4974       default:
4975       case IcsExamining:
4976         break;
4977     }
4978     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4979         && gameMode != AnalyzeFile && gameMode != Training) {
4980         DisplayMoveError(_("Displayed position is not current"));
4981         return FALSE;
4982     }
4983     return TRUE;
4984 }
4985
4986 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4987 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4988 int lastLoadGameUseList = FALSE;
4989 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4990 ChessMove lastLoadGameStart = (ChessMove) 0;
4991
4992 ChessMove
4993 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4994      int fromX, fromY, toX, toY;
4995      int promoChar;
4996      Boolean captureOwn;
4997 {
4998     ChessMove moveType;
4999     ChessSquare pdown, pup;
5000
5001     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5002
5003     /* [HGM] suppress all moves into holdings area and guard band */
5004     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5005             return ImpossibleMove;
5006
5007     /* [HGM] <sameColor> moved to here from winboard.c */
5008     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5009     pdown = boards[currentMove][fromY][fromX];
5010     pup = boards[currentMove][toY][toX];
5011     if (    gameMode != EditPosition && !captureOwn &&
5012             (WhitePawn <= pdown && pdown < BlackPawn &&
5013              WhitePawn <= pup && pup < BlackPawn  ||
5014              BlackPawn <= pdown && pdown < EmptySquare &&
5015              BlackPawn <= pup && pup < EmptySquare 
5016             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5017                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5018                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5019                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5020                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5021         )           )
5022          return Comment;
5023
5024     /* Check if the user is playing in turn.  This is complicated because we
5025        let the user "pick up" a piece before it is his turn.  So the piece he
5026        tried to pick up may have been captured by the time he puts it down!
5027        Therefore we use the color the user is supposed to be playing in this
5028        test, not the color of the piece that is currently on the starting
5029        square---except in EditGame mode, where the user is playing both
5030        sides; fortunately there the capture race can't happen.  (It can
5031        now happen in IcsExamining mode, but that's just too bad.  The user
5032        will get a somewhat confusing message in that case.)
5033        */
5034
5035     switch (gameMode) {
5036       case PlayFromGameFile:
5037       case AnalyzeFile:
5038       case TwoMachinesPlay:
5039       case EndOfGame:
5040       case IcsObserving:
5041       case IcsIdle:
5042         /* We switched into a game mode where moves are not accepted,
5043            perhaps while the mouse button was down. */
5044         return ImpossibleMove;
5045
5046       case MachinePlaysWhite:
5047         /* User is moving for Black */
5048         if (WhiteOnMove(currentMove)) {
5049             DisplayMoveError(_("It is White's turn"));
5050             return ImpossibleMove;
5051         }
5052         break;
5053
5054       case MachinePlaysBlack:
5055         /* User is moving for White */
5056         if (!WhiteOnMove(currentMove)) {
5057             DisplayMoveError(_("It is Black's turn"));
5058             return ImpossibleMove;
5059         }
5060         break;
5061
5062       case EditGame:
5063       case IcsExamining:
5064       case BeginningOfGame:
5065       case AnalyzeMode:
5066       case Training:
5067         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5068             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5069             /* User is moving for Black */
5070             if (WhiteOnMove(currentMove)) {
5071                 DisplayMoveError(_("It is White's turn"));
5072                 return ImpossibleMove;
5073             }
5074         } else {
5075             /* User is moving for White */
5076             if (!WhiteOnMove(currentMove)) {
5077                 DisplayMoveError(_("It is Black's turn"));
5078                 return ImpossibleMove;
5079             }
5080         }
5081         break;
5082
5083       case IcsPlayingBlack:
5084         /* User is moving for Black */
5085         if (WhiteOnMove(currentMove)) {
5086             if (!appData.premove) {
5087                 DisplayMoveError(_("It is White's turn"));
5088             } else if (toX >= 0 && toY >= 0) {
5089                 premoveToX = toX;
5090                 premoveToY = toY;
5091                 premoveFromX = fromX;
5092                 premoveFromY = fromY;
5093                 premovePromoChar = promoChar;
5094                 gotPremove = 1;
5095                 if (appData.debugMode) 
5096                     fprintf(debugFP, "Got premove: fromX %d,"
5097                             "fromY %d, toX %d, toY %d\n",
5098                             fromX, fromY, toX, toY);
5099             }
5100             return ImpossibleMove;
5101         }
5102         break;
5103
5104       case IcsPlayingWhite:
5105         /* User is moving for White */
5106         if (!WhiteOnMove(currentMove)) {
5107             if (!appData.premove) {
5108                 DisplayMoveError(_("It is Black's turn"));
5109             } else if (toX >= 0 && toY >= 0) {
5110                 premoveToX = toX;
5111                 premoveToY = toY;
5112                 premoveFromX = fromX;
5113                 premoveFromY = fromY;
5114                 premovePromoChar = promoChar;
5115                 gotPremove = 1;
5116                 if (appData.debugMode) 
5117                     fprintf(debugFP, "Got premove: fromX %d,"
5118                             "fromY %d, toX %d, toY %d\n",
5119                             fromX, fromY, toX, toY);
5120             }
5121             return ImpossibleMove;
5122         }
5123         break;
5124
5125       default:
5126         break;
5127
5128       case EditPosition:
5129         /* EditPosition, empty square, or different color piece;
5130            click-click move is possible */
5131         if (toX == -2 || toY == -2) {
5132             boards[0][fromY][fromX] = EmptySquare;
5133             return AmbiguousMove;
5134         } else if (toX >= 0 && toY >= 0) {
5135             boards[0][toY][toX] = boards[0][fromY][fromX];
5136             boards[0][fromY][fromX] = EmptySquare;
5137             return AmbiguousMove;
5138         }
5139         return ImpossibleMove;
5140     }
5141
5142     /* [HGM] If move started in holdings, it means a drop */
5143     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5144          if( pup != EmptySquare ) return ImpossibleMove;
5145          if(appData.testLegality) {
5146              /* it would be more logical if LegalityTest() also figured out
5147               * which drops are legal. For now we forbid pawns on back rank.
5148               * Shogi is on its own here...
5149               */
5150              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5151                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5152                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5153          }
5154          return WhiteDrop; /* Not needed to specify white or black yet */
5155     }
5156
5157     userOfferedDraw = FALSE;
5158         
5159     /* [HGM] always test for legality, to get promotion info */
5160     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5161                           epStatus[currentMove], castlingRights[currentMove],
5162                                          fromY, fromX, toY, toX, promoChar);
5163     /* [HGM] but possibly ignore an IllegalMove result */
5164     if (appData.testLegality) {
5165         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5166             DisplayMoveError(_("Illegal move"));
5167             return ImpossibleMove;
5168         }
5169     }
5170 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5171     return moveType;
5172     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5173        function is made into one that returns an OK move type if FinishMove
5174        should be called. This to give the calling driver routine the
5175        opportunity to finish the userMove input with a promotion popup,
5176        without bothering the user with this for invalid or illegal moves */
5177
5178 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5179 }
5180
5181 /* Common tail of UserMoveEvent and DropMenuEvent */
5182 int
5183 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5184      ChessMove moveType;
5185      int fromX, fromY, toX, toY;
5186      /*char*/int promoChar;
5187 {
5188     char *bookHit = 0;
5189 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5190     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5191         // [HGM] superchess: suppress promotions to non-available piece
5192         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5193         if(WhiteOnMove(currentMove)) {
5194             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5195         } else {
5196             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5197         }
5198     }
5199
5200     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5201        move type in caller when we know the move is a legal promotion */
5202     if(moveType == NormalMove && promoChar)
5203         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5204 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5205     /* [HGM] convert drag-and-drop piece drops to standard form */
5206     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5207          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5208            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5209                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5210 //         fromX = boards[currentMove][fromY][fromX];
5211            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5212            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5213            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5214            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5215          fromY = DROP_RANK;
5216     }
5217
5218     /* [HGM] <popupFix> The following if has been moved here from
5219        UserMoveEvent(). Because it seemed to belon here (why not allow
5220        piece drops in training games?), and because it can only be
5221        performed after it is known to what we promote. */
5222     if (gameMode == Training) {
5223       /* compare the move played on the board to the next move in the
5224        * game. If they match, display the move and the opponent's response. 
5225        * If they don't match, display an error message.
5226        */
5227       int saveAnimate;
5228       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5229       CopyBoard(testBoard, boards[currentMove]);
5230       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5231
5232       if (CompareBoards(testBoard, boards[currentMove+1])) {
5233         ForwardInner(currentMove+1);
5234
5235         /* Autoplay the opponent's response.
5236          * if appData.animate was TRUE when Training mode was entered,
5237          * the response will be animated.
5238          */
5239         saveAnimate = appData.animate;
5240         appData.animate = animateTraining;
5241         ForwardInner(currentMove+1);
5242         appData.animate = saveAnimate;
5243
5244         /* check for the end of the game */
5245         if (currentMove >= forwardMostMove) {
5246           gameMode = PlayFromGameFile;
5247           ModeHighlight();
5248           SetTrainingModeOff();
5249           DisplayInformation(_("End of game"));
5250         }
5251       } else {
5252         DisplayError(_("Incorrect move"), 0);
5253       }
5254       return 1;
5255     }
5256
5257   /* Ok, now we know that the move is good, so we can kill
5258      the previous line in Analysis Mode */
5259   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5260     forwardMostMove = currentMove;
5261   }
5262
5263   /* If we need the chess program but it's dead, restart it */
5264   ResurrectChessProgram();
5265
5266   /* A user move restarts a paused game*/
5267   if (pausing)
5268     PauseEvent();
5269
5270   thinkOutput[0] = NULLCHAR;
5271
5272   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5273
5274   if (gameMode == BeginningOfGame) {
5275     if (appData.noChessProgram) {
5276       gameMode = EditGame;
5277       SetGameInfo();
5278     } else {
5279       char buf[MSG_SIZ];
5280       gameMode = MachinePlaysBlack;
5281       StartClocks();
5282       SetGameInfo();
5283       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5284       DisplayTitle(buf);
5285       if (first.sendName) {
5286         sprintf(buf, "name %s\n", gameInfo.white);
5287         SendToProgram(buf, &first);
5288       }
5289       StartClocks();
5290     }
5291     ModeHighlight();
5292   }
5293 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5294   /* Relay move to ICS or chess engine */
5295   if (appData.icsActive) {
5296     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5297         gameMode == IcsExamining) {
5298       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5299       ics_user_moved = 1;
5300     }
5301   } else {
5302     if (first.sendTime && (gameMode == BeginningOfGame ||
5303                            gameMode == MachinePlaysWhite ||
5304                            gameMode == MachinePlaysBlack)) {
5305       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5306     }
5307     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5308          // [HGM] book: if program might be playing, let it use book
5309         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5310         first.maybeThinking = TRUE;
5311     } else SendMoveToProgram(forwardMostMove-1, &first);
5312     if (currentMove == cmailOldMove + 1) {
5313       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5314     }
5315   }
5316
5317   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5318
5319   switch (gameMode) {
5320   case EditGame:
5321     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5322                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5323     case MT_NONE:
5324     case MT_CHECK:
5325       break;
5326     case MT_CHECKMATE:
5327     case MT_STAINMATE:
5328       if (WhiteOnMove(currentMove)) {
5329         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5330       } else {
5331         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5332       }
5333       break;
5334     case MT_STALEMATE:
5335       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5336       break;
5337     }
5338     break;
5339     
5340   case MachinePlaysBlack:
5341   case MachinePlaysWhite:
5342     /* disable certain menu options while machine is thinking */
5343     SetMachineThinkingEnables();
5344     break;
5345
5346   default:
5347     break;
5348   }
5349
5350   if(bookHit) { // [HGM] book: simulate book reply
5351         static char bookMove[MSG_SIZ]; // a bit generous?
5352
5353         programStats.nodes = programStats.depth = programStats.time = 
5354         programStats.score = programStats.got_only_move = 0;
5355         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5356
5357         strcpy(bookMove, "move ");
5358         strcat(bookMove, bookHit);
5359         HandleMachineMove(bookMove, &first);
5360   }
5361   return 1;
5362 }
5363
5364 void
5365 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5366      int fromX, fromY, toX, toY;
5367      int promoChar;
5368 {
5369     /* [HGM] This routine was added to allow calling of its two logical
5370        parts from other modules in the old way. Before, UserMoveEvent()
5371        automatically called FinishMove() if the move was OK, and returned
5372        otherwise. I separated the two, in order to make it possible to
5373        slip a promotion popup in between. But that it always needs two
5374        calls, to the first part, (now called UserMoveTest() ), and to
5375        FinishMove if the first part succeeded. Calls that do not need
5376        to do anything in between, can call this routine the old way. 
5377     */
5378     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5379 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5380     if(moveType == AmbiguousMove)
5381         DrawPosition(FALSE, boards[currentMove]);
5382     else if(moveType != ImpossibleMove && moveType != Comment)
5383         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5384 }
5385
5386 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5387 {
5388 //    char * hint = lastHint;
5389     FrontEndProgramStats stats;
5390
5391     stats.which = cps == &first ? 0 : 1;
5392     stats.depth = cpstats->depth;
5393     stats.nodes = cpstats->nodes;
5394     stats.score = cpstats->score;
5395     stats.time = cpstats->time;
5396     stats.pv = cpstats->movelist;
5397     stats.hint = lastHint;
5398     stats.an_move_index = 0;
5399     stats.an_move_count = 0;
5400
5401     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5402         stats.hint = cpstats->move_name;
5403         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5404         stats.an_move_count = cpstats->nr_moves;
5405     }
5406
5407     SetProgramStats( &stats );
5408 }
5409
5410 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5411 {   // [HGM] book: this routine intercepts moves to simulate book replies
5412     char *bookHit = NULL;
5413
5414     //first determine if the incoming move brings opponent into his book
5415     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5416         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5417     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5418     if(bookHit != NULL && !cps->bookSuspend) {
5419         // make sure opponent is not going to reply after receiving move to book position
5420         SendToProgram("force\n", cps);
5421         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5422     }
5423     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5424     // now arrange restart after book miss
5425     if(bookHit) {
5426         // after a book hit we never send 'go', and the code after the call to this routine
5427         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5428         char buf[MSG_SIZ];
5429         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5430         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5431         SendToProgram(buf, cps);
5432         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5433     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5434         SendToProgram("go\n", cps);
5435         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5436     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5437         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5438             SendToProgram("go\n", cps); 
5439         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5440     }
5441     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5442 }
5443
5444 char *savedMessage;
5445 ChessProgramState *savedState;
5446 void DeferredBookMove(void)
5447 {
5448         if(savedState->lastPing != savedState->lastPong)
5449                     ScheduleDelayedEvent(DeferredBookMove, 10);
5450         else
5451         HandleMachineMove(savedMessage, savedState);
5452 }
5453
5454 void
5455 HandleMachineMove(message, cps)
5456      char *message;
5457      ChessProgramState *cps;
5458 {
5459     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5460     char realname[MSG_SIZ];
5461     int fromX, fromY, toX, toY;
5462     ChessMove moveType;
5463     char promoChar;
5464     char *p;
5465     int machineWhite;
5466     char *bookHit;
5467
5468 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5469     /*
5470      * Kludge to ignore BEL characters
5471      */
5472     while (*message == '\007') message++;
5473
5474     /*
5475      * [HGM] engine debug message: ignore lines starting with '#' character
5476      */
5477     if(cps->debug && *message == '#') return;
5478
5479     /*
5480      * Look for book output
5481      */
5482     if (cps == &first && bookRequested) {
5483         if (message[0] == '\t' || message[0] == ' ') {
5484             /* Part of the book output is here; append it */
5485             strcat(bookOutput, message);
5486             strcat(bookOutput, "  \n");
5487             return;
5488         } else if (bookOutput[0] != NULLCHAR) {
5489             /* All of book output has arrived; display it */
5490             char *p = bookOutput;
5491             while (*p != NULLCHAR) {
5492                 if (*p == '\t') *p = ' ';
5493                 p++;
5494             }
5495             DisplayInformation(bookOutput);
5496             bookRequested = FALSE;
5497             /* Fall through to parse the current output */
5498         }
5499     }
5500
5501     /*
5502      * Look for machine move.
5503      */
5504     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5505         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5506     {
5507         /* This method is only useful on engines that support ping */
5508         if (cps->lastPing != cps->lastPong) {
5509           if (gameMode == BeginningOfGame) {
5510             /* Extra move from before last new; ignore */
5511             if (appData.debugMode) {
5512                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5513             }
5514           } else {
5515             if (appData.debugMode) {
5516                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5517                         cps->which, gameMode);
5518             }
5519
5520             SendToProgram("undo\n", cps);
5521           }
5522           return;
5523         }
5524
5525         switch (gameMode) {
5526           case BeginningOfGame:
5527             /* Extra move from before last reset; ignore */
5528             if (appData.debugMode) {
5529                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5530             }
5531             return;
5532
5533           case EndOfGame:
5534           case IcsIdle:
5535           default:
5536             /* Extra move after we tried to stop.  The mode test is
5537                not a reliable way of detecting this problem, but it's
5538                the best we can do on engines that don't support ping.
5539             */
5540             if (appData.debugMode) {
5541                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5542                         cps->which, gameMode);
5543             }
5544             SendToProgram("undo\n", cps);
5545             return;
5546
5547           case MachinePlaysWhite:
5548           case IcsPlayingWhite:
5549             machineWhite = TRUE;
5550             break;
5551
5552           case MachinePlaysBlack:
5553           case IcsPlayingBlack:
5554             machineWhite = FALSE;
5555             break;
5556
5557           case TwoMachinesPlay:
5558             machineWhite = (cps->twoMachinesColor[0] == 'w');
5559             break;
5560         }
5561         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5562             if (appData.debugMode) {
5563                 fprintf(debugFP,
5564                         "Ignoring move out of turn by %s, gameMode %d"
5565                         ", forwardMost %d\n",
5566                         cps->which, gameMode, forwardMostMove);
5567             }
5568             return;
5569         }
5570
5571     if (appData.debugMode) { int f = forwardMostMove;
5572         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5573                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5574     }
5575         if(cps->alphaRank) AlphaRank(machineMove, 4);
5576         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5577                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5578             /* Machine move could not be parsed; ignore it. */
5579             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5580                     machineMove, cps->which);
5581             DisplayError(buf1, 0);
5582             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5583                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5584             if (gameMode == TwoMachinesPlay) {
5585               GameEnds(machineWhite ? BlackWins : WhiteWins,
5586                        buf1, GE_XBOARD);
5587             }
5588             return;
5589         }
5590
5591         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5592         /* So we have to redo legality test with true e.p. status here,  */
5593         /* to make sure an illegal e.p. capture does not slip through,   */
5594         /* to cause a forfeit on a justified illegal-move complaint      */
5595         /* of the opponent.                                              */
5596         if( gameMode==TwoMachinesPlay && appData.testLegality
5597             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5598                                                               ) {
5599            ChessMove moveType;
5600            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5601                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5602                              fromY, fromX, toY, toX, promoChar);
5603             if (appData.debugMode) {
5604                 int i;
5605                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5606                     castlingRights[forwardMostMove][i], castlingRank[i]);
5607                 fprintf(debugFP, "castling rights\n");
5608             }
5609             if(moveType == IllegalMove) {
5610                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5611                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5612                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5613                            buf1, GE_XBOARD);
5614                 return;
5615            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5616            /* [HGM] Kludge to handle engines that send FRC-style castling
5617               when they shouldn't (like TSCP-Gothic) */
5618            switch(moveType) {
5619              case WhiteASideCastleFR:
5620              case BlackASideCastleFR:
5621                toX+=2;
5622                currentMoveString[2]++;
5623                break;
5624              case WhiteHSideCastleFR:
5625              case BlackHSideCastleFR:
5626                toX--;
5627                currentMoveString[2]--;
5628                break;
5629              default: ; // nothing to do, but suppresses warning of pedantic compilers
5630            }
5631         }
5632         hintRequested = FALSE;
5633         lastHint[0] = NULLCHAR;
5634         bookRequested = FALSE;
5635         /* Program may be pondering now */
5636         cps->maybeThinking = TRUE;
5637         if (cps->sendTime == 2) cps->sendTime = 1;
5638         if (cps->offeredDraw) cps->offeredDraw--;
5639
5640 #if ZIPPY
5641         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5642             first.initDone) {
5643           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5644           ics_user_moved = 1;
5645           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5646                 char buf[3*MSG_SIZ];
5647
5648                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5649                         programStats.score / 100.,
5650                         programStats.depth,
5651                         programStats.time / 100.,
5652                         (unsigned int)programStats.nodes,
5653                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5654                         programStats.movelist);
5655                 SendToICS(buf);
5656 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5657           }
5658         }
5659 #endif
5660         /* currentMoveString is set as a side-effect of ParseOneMove */
5661         strcpy(machineMove, currentMoveString);
5662         strcat(machineMove, "\n");
5663         strcpy(moveList[forwardMostMove], machineMove);
5664
5665         /* [AS] Save move info and clear stats for next move */
5666         pvInfoList[ forwardMostMove ].score = programStats.score;
5667         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5668         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5669         ClearProgramStats();
5670         thinkOutput[0] = NULLCHAR;
5671         hiddenThinkOutputState = 0;
5672
5673         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5674
5675         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5676         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5677             int count = 0;
5678
5679             while( count < adjudicateLossPlies ) {
5680                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5681
5682                 if( count & 1 ) {
5683                     score = -score; /* Flip score for winning side */
5684                 }
5685
5686                 if( score > adjudicateLossThreshold ) {
5687                     break;
5688                 }
5689
5690                 count++;
5691             }
5692
5693             if( count >= adjudicateLossPlies ) {
5694                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5695
5696                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5697                     "Xboard adjudication", 
5698                     GE_XBOARD );
5699
5700                 return;
5701             }
5702         }
5703
5704         if( gameMode == TwoMachinesPlay ) {
5705           // [HGM] some adjudications useful with buggy engines
5706             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5707           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5708
5709
5710             if( appData.testLegality )
5711             {   /* [HGM] Some more adjudications for obstinate engines */
5712                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5713                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5714                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5715                 static int moveCount = 6;
5716                 ChessMove result;
5717                 char *reason = NULL;
5718
5719                 /* Count what is on board. */
5720                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5721                 {   ChessSquare p = boards[forwardMostMove][i][j];
5722                     int m=i;
5723
5724                     switch((int) p)
5725                     {   /* count B,N,R and other of each side */
5726                         case WhiteKing:
5727                         case BlackKing:
5728                              NrK++; break; // [HGM] atomic: count Kings
5729                         case WhiteKnight:
5730                              NrWN++; break;
5731                         case WhiteBishop:
5732                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5733                              bishopsColor |= 1 << ((i^j)&1);
5734                              NrWB++; break;
5735                         case BlackKnight:
5736                              NrBN++; break;
5737                         case BlackBishop:
5738                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5739                              bishopsColor |= 1 << ((i^j)&1);
5740                              NrBB++; break;
5741                         case WhiteRook:
5742                              NrWR++; break;
5743                         case BlackRook:
5744                              NrBR++; break;
5745                         case WhiteQueen:
5746                              NrWQ++; break;
5747                         case BlackQueen:
5748                              NrBQ++; break;
5749                         case EmptySquare: 
5750                              break;
5751                         case BlackPawn:
5752                              m = 7-i;
5753                         case WhitePawn:
5754                              PawnAdvance += m; NrPawns++;
5755                     }
5756                     NrPieces += (p != EmptySquare);
5757                     NrW += ((int)p < (int)BlackPawn);
5758                     if(gameInfo.variant == VariantXiangqi && 
5759                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5760                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5761                         NrW -= ((int)p < (int)BlackPawn);
5762                     }
5763                 }
5764
5765                 /* Some material-based adjudications that have to be made before stalemate test */
5766                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5767                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5768                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5769                      if(appData.checkMates) {
5770                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5771                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5772                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5773                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5774                          return;
5775                      }
5776                 }
5777
5778                 /* Bare King in Shatranj (loses) or Losers (wins) */
5779                 if( NrW == 1 || NrPieces - NrW == 1) {
5780                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5781                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5782                      if(appData.checkMates) {
5783                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5784                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5785                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5786                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5787                          return;
5788                      }
5789                   } else
5790                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5791                   {    /* bare King */
5792                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5793                         if(appData.checkMates) {
5794                             /* but only adjudicate if adjudication enabled */
5795                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5796                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5797                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5798                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5799                             return;
5800                         }
5801                   }
5802                 } else bare = 1;
5803
5804
5805             // don't wait for engine to announce game end if we can judge ourselves
5806             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5807                                        castlingRights[forwardMostMove]) ) {
5808               case MT_CHECK:
5809                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5810                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5811                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5812                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5813                             checkCnt++;
5814                         if(checkCnt >= 2) {
5815                             reason = "Xboard adjudication: 3rd check";
5816                             epStatus[forwardMostMove] = EP_CHECKMATE;
5817                             break;
5818                         }
5819                     }
5820                 }
5821               case MT_NONE:
5822               default:
5823                 break;
5824               case MT_STALEMATE:
5825               case MT_STAINMATE:
5826                 reason = "Xboard adjudication: Stalemate";
5827                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5828                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5829                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5830                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5831                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5832                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5833                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5834                                                                         EP_CHECKMATE : EP_WINS);
5835                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5836                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5837                 }
5838                 break;
5839               case MT_CHECKMATE:
5840                 reason = "Xboard adjudication: Checkmate";
5841                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5842                 break;
5843             }
5844
5845                 switch(i = epStatus[forwardMostMove]) {
5846                     case EP_STALEMATE:
5847                         result = GameIsDrawn; break;
5848                     case EP_CHECKMATE:
5849                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5850                     case EP_WINS:
5851                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5852                     default:
5853                         result = (ChessMove) 0;
5854                 }
5855                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5856                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5857                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5858                     GameEnds( result, reason, GE_XBOARD );
5859                     return;
5860                 }
5861
5862                 /* Next absolutely insufficient mating material. */
5863                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5864                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5865                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5866                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5867                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5868
5869                      /* always flag draws, for judging claims */
5870                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5871
5872                      if(appData.materialDraws) {
5873                          /* but only adjudicate them if adjudication enabled */
5874                          SendToProgram("force\n", cps->other); // suppress reply
5875                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5876                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5877                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5878                          return;
5879                      }
5880                 }
5881
5882                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5883                 if(NrPieces == 4 && 
5884                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5885                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5886                    || NrWN==2 || NrBN==2     /* KNNK */
5887                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5888                   ) ) {
5889                      if(--moveCount < 0 && appData.trivialDraws)
5890                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5891                           SendToProgram("force\n", cps->other); // suppress reply
5892                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5893                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5894                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5895                           return;
5896                      }
5897                 } else moveCount = 6;
5898             }
5899           }
5900           
5901           if (appData.debugMode) { int i;
5902             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5903                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5904                     appData.drawRepeats);
5905             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5906               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5907             
5908           }
5909
5910                 /* Check for rep-draws */
5911                 count = 0;
5912                 for(k = forwardMostMove-2;
5913                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5914                         epStatus[k] < EP_UNKNOWN &&
5915                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5916                     k-=2)
5917                 {   int rights=0;
5918                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5919                         /* compare castling rights */
5920                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5921                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5922                                 rights++; /* King lost rights, while rook still had them */
5923                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5924                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5925                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5926                                    rights++; /* but at least one rook lost them */
5927                         }
5928                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5929                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5930                                 rights++; 
5931                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5932                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5933                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5934                                    rights++;
5935                         }
5936                         if( rights == 0 && ++count > appData.drawRepeats-2
5937                             && appData.drawRepeats > 1) {
5938                              /* adjudicate after user-specified nr of repeats */
5939                              SendToProgram("force\n", cps->other); // suppress reply
5940                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5941                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5942                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5943                                 // [HGM] xiangqi: check for forbidden perpetuals
5944                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5945                                 for(m=forwardMostMove; m>k; m-=2) {
5946                                     if(MateTest(boards[m], PosFlags(m), 
5947                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5948                                         ourPerpetual = 0; // the current mover did not always check
5949                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5950                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5951                                         hisPerpetual = 0; // the opponent did not always check
5952                                 }
5953                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5954                                                                         ourPerpetual, hisPerpetual);
5955                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5956                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5957                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5958                                     return;
5959                                 }
5960                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5961                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5962                                 // Now check for perpetual chases
5963                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5964                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5965                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5966                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5967                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5968                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5969                                         return;
5970                                     }
5971                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5972                                         break; // Abort repetition-checking loop.
5973                                 }
5974                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5975                              }
5976                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5977                              return;
5978                         }
5979                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5980                              epStatus[forwardMostMove] = EP_REP_DRAW;
5981                     }
5982                 }
5983
5984                 /* Now we test for 50-move draws. Determine ply count */
5985                 count = forwardMostMove;
5986                 /* look for last irreversble move */
5987                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5988                     count--;
5989                 /* if we hit starting position, add initial plies */
5990                 if( count == backwardMostMove )
5991                     count -= initialRulePlies;
5992                 count = forwardMostMove - count; 
5993                 if( count >= 100)
5994                          epStatus[forwardMostMove] = EP_RULE_DRAW;
5995                          /* this is used to judge if draw claims are legal */
5996                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5997                          SendToProgram("force\n", cps->other); // suppress reply
5998                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5999                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6000                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6001                          return;
6002                 }
6003
6004                 /* if draw offer is pending, treat it as a draw claim
6005                  * when draw condition present, to allow engines a way to
6006                  * claim draws before making their move to avoid a race
6007                  * condition occurring after their move
6008                  */
6009                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6010                          char *p = NULL;
6011                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6012                              p = "Draw claim: 50-move rule";
6013                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6014                              p = "Draw claim: 3-fold repetition";
6015                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6016                              p = "Draw claim: insufficient mating material";
6017                          if( p != NULL ) {
6018                              SendToProgram("force\n", cps->other); // suppress reply
6019                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6020                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6021                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6022                              return;
6023                          }
6024                 }
6025
6026
6027                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6028                     SendToProgram("force\n", cps->other); // suppress reply
6029                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6030                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6031
6032                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6033
6034                     return;
6035                 }
6036         }
6037
6038         bookHit = NULL;
6039         if (gameMode == TwoMachinesPlay) {
6040             /* [HGM] relaying draw offers moved to after reception of move */
6041             /* and interpreting offer as claim if it brings draw condition */
6042             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6043                 SendToProgram("draw\n", cps->other);
6044             }
6045             if (cps->other->sendTime) {
6046                 SendTimeRemaining(cps->other,
6047                                   cps->other->twoMachinesColor[0] == 'w');
6048             }
6049             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6050             if (firstMove && !bookHit) {
6051                 firstMove = FALSE;
6052                 if (cps->other->useColors) {
6053                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6054                 }
6055                 SendToProgram("go\n", cps->other);
6056             }
6057             cps->other->maybeThinking = TRUE;
6058         }
6059
6060         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6061         
6062         if (!pausing && appData.ringBellAfterMoves) {
6063             RingBell();
6064         }
6065
6066         /* 
6067          * Reenable menu items that were disabled while
6068          * machine was thinking
6069          */
6070         if (gameMode != TwoMachinesPlay)
6071             SetUserThinkingEnables();
6072
6073         // [HGM] book: after book hit opponent has received move and is now in force mode
6074         // force the book reply into it, and then fake that it outputted this move by jumping
6075         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6076         if(bookHit) {
6077                 static char bookMove[MSG_SIZ]; // a bit generous?
6078
6079                 strcpy(bookMove, "move ");
6080                 strcat(bookMove, bookHit);
6081                 message = bookMove;
6082                 cps = cps->other;
6083                 programStats.nodes = programStats.depth = programStats.time = 
6084                 programStats.score = programStats.got_only_move = 0;
6085                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6086
6087                 if(cps->lastPing != cps->lastPong) {
6088                     savedMessage = message; // args for deferred call
6089                     savedState = cps;
6090                     ScheduleDelayedEvent(DeferredBookMove, 10);
6091                     return;
6092                 }
6093                 goto FakeBookMove;
6094         }
6095
6096         return;
6097     }
6098
6099     /* Set special modes for chess engines.  Later something general
6100      *  could be added here; for now there is just one kludge feature,
6101      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6102      *  when "xboard" is given as an interactive command.
6103      */
6104     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6105         cps->useSigint = FALSE;
6106         cps->useSigterm = FALSE;
6107     }
6108     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6109       ParseFeatures(message+8, cps);
6110       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6111     }
6112
6113     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6114      * want this, I was asked to put it in, and obliged.
6115      */
6116     if (!strncmp(message, "setboard ", 9)) {
6117         Board initial_position; int i;
6118
6119         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6120
6121         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6122             DisplayError(_("Bad FEN received from engine"), 0);
6123             return ;
6124         } else {
6125            Reset(FALSE, FALSE);
6126            CopyBoard(boards[0], initial_position);
6127            initialRulePlies = FENrulePlies;
6128            epStatus[0] = FENepStatus;
6129            for( i=0; i<nrCastlingRights; i++ )
6130                 castlingRights[0][i] = FENcastlingRights[i];
6131            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6132            else gameMode = MachinePlaysBlack;                 
6133            DrawPosition(FALSE, boards[currentMove]);
6134         }
6135         return;
6136     }
6137
6138     /*
6139      * Look for communication commands
6140      */
6141     if (!strncmp(message, "telluser ", 9)) {
6142         DisplayNote(message + 9);
6143         return;
6144     }
6145     if (!strncmp(message, "tellusererror ", 14)) {
6146         DisplayError(message + 14, 0);
6147         return;
6148     }
6149     if (!strncmp(message, "tellopponent ", 13)) {
6150       if (appData.icsActive) {
6151         if (loggedOn) {
6152           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6153           SendToICS(buf1);
6154         }
6155       } else {
6156         DisplayNote(message + 13);
6157       }
6158       return;
6159     }
6160     if (!strncmp(message, "tellothers ", 11)) {
6161       if (appData.icsActive) {
6162         if (loggedOn) {
6163           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6164           SendToICS(buf1);
6165         }
6166       }
6167       return;
6168     }
6169     if (!strncmp(message, "tellall ", 8)) {
6170       if (appData.icsActive) {
6171         if (loggedOn) {
6172           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6173           SendToICS(buf1);
6174         }
6175       } else {
6176         DisplayNote(message + 8);
6177       }
6178       return;
6179     }
6180     if (strncmp(message, "warning", 7) == 0) {
6181         /* Undocumented feature, use tellusererror in new code */
6182         DisplayError(message, 0);
6183         return;
6184     }
6185     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6186         strcpy(realname, cps->tidy);
6187         strcat(realname, " query");
6188         AskQuestion(realname, buf2, buf1, cps->pr);
6189         return;
6190     }
6191     /* Commands from the engine directly to ICS.  We don't allow these to be 
6192      *  sent until we are logged on. Crafty kibitzes have been known to 
6193      *  interfere with the login process.
6194      */
6195     if (loggedOn) {
6196         if (!strncmp(message, "tellics ", 8)) {
6197             SendToICS(message + 8);
6198             SendToICS("\n");
6199             return;
6200         }
6201         if (!strncmp(message, "tellicsnoalias ", 15)) {
6202             SendToICS(ics_prefix);
6203             SendToICS(message + 15);
6204             SendToICS("\n");
6205             return;
6206         }
6207         /* The following are for backward compatibility only */
6208         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6209             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6210             SendToICS(ics_prefix);
6211             SendToICS(message);
6212             SendToICS("\n");
6213             return;
6214         }
6215     }
6216     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6217         return;
6218     }
6219     /*
6220      * If the move is illegal, cancel it and redraw the board.
6221      * Also deal with other error cases.  Matching is rather loose
6222      * here to accommodate engines written before the spec.
6223      */
6224     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6225         strncmp(message, "Error", 5) == 0) {
6226         if (StrStr(message, "name") || 
6227             StrStr(message, "rating") || StrStr(message, "?") ||
6228             StrStr(message, "result") || StrStr(message, "board") ||
6229             StrStr(message, "bk") || StrStr(message, "computer") ||
6230             StrStr(message, "variant") || StrStr(message, "hint") ||
6231             StrStr(message, "random") || StrStr(message, "depth") ||
6232             StrStr(message, "accepted")) {
6233             return;
6234         }
6235         if (StrStr(message, "protover")) {
6236           /* Program is responding to input, so it's apparently done
6237              initializing, and this error message indicates it is
6238              protocol version 1.  So we don't need to wait any longer
6239              for it to initialize and send feature commands. */
6240           FeatureDone(cps, 1);
6241           cps->protocolVersion = 1;
6242           return;
6243         }
6244         cps->maybeThinking = FALSE;
6245
6246         if (StrStr(message, "draw")) {
6247             /* Program doesn't have "draw" command */
6248             cps->sendDrawOffers = 0;
6249             return;
6250         }
6251         if (cps->sendTime != 1 &&
6252             (StrStr(message, "time") || StrStr(message, "otim"))) {
6253           /* Program apparently doesn't have "time" or "otim" command */
6254           cps->sendTime = 0;
6255           return;
6256         }
6257         if (StrStr(message, "analyze")) {
6258             cps->analysisSupport = FALSE;
6259             cps->analyzing = FALSE;
6260             Reset(FALSE, TRUE);
6261             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6262             DisplayError(buf2, 0);
6263             return;
6264         }
6265         if (StrStr(message, "(no matching move)st")) {
6266           /* Special kludge for GNU Chess 4 only */
6267           cps->stKludge = TRUE;
6268           SendTimeControl(cps, movesPerSession, timeControl,
6269                           timeIncrement, appData.searchDepth,
6270                           searchTime);
6271           return;
6272         }
6273         if (StrStr(message, "(no matching move)sd")) {
6274           /* Special kludge for GNU Chess 4 only */
6275           cps->sdKludge = TRUE;
6276           SendTimeControl(cps, movesPerSession, timeControl,
6277                           timeIncrement, appData.searchDepth,
6278                           searchTime);
6279           return;
6280         }
6281         if (!StrStr(message, "llegal")) {
6282             return;
6283         }
6284         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6285             gameMode == IcsIdle) return;
6286         if (forwardMostMove <= backwardMostMove) return;
6287         if (pausing) PauseEvent();
6288       if(appData.forceIllegal) {
6289             // [HGM] illegal: machine refused move; force position after move into it
6290           SendToProgram("force\n", cps);
6291           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6292                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6293                 // when black is to move, while there might be nothing on a2 or black
6294                 // might already have the move. So send the board as if white has the move.
6295                 // But first we must change the stm of the engine, as it refused the last move
6296                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6297                 if(WhiteOnMove(forwardMostMove)) {
6298                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6299                     SendBoard(cps, forwardMostMove); // kludgeless board
6300                 } else {
6301                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6302                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6303                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6304                 }
6305           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6306             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6307                  gameMode == TwoMachinesPlay)
6308               SendToProgram("go\n", cps);
6309             return;
6310       } else
6311         if (gameMode == PlayFromGameFile) {
6312             /* Stop reading this game file */
6313             gameMode = EditGame;
6314             ModeHighlight();
6315         }
6316         currentMove = --forwardMostMove;
6317         DisplayMove(currentMove-1); /* before DisplayMoveError */
6318         SwitchClocks();
6319         DisplayBothClocks();
6320         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6321                 parseList[currentMove], cps->which);
6322         DisplayMoveError(buf1);
6323         DrawPosition(FALSE, boards[currentMove]);
6324
6325         /* [HGM] illegal-move claim should forfeit game when Xboard */
6326         /* only passes fully legal moves                            */
6327         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6328             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6329                                 "False illegal-move claim", GE_XBOARD );
6330         }
6331         return;
6332     }
6333     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6334         /* Program has a broken "time" command that
6335            outputs a string not ending in newline.
6336            Don't use it. */
6337         cps->sendTime = 0;
6338     }
6339     
6340     /*
6341      * If chess program startup fails, exit with an error message.
6342      * Attempts to recover here are futile.
6343      */
6344     if ((StrStr(message, "unknown host") != NULL)
6345         || (StrStr(message, "No remote directory") != NULL)
6346         || (StrStr(message, "not found") != NULL)
6347         || (StrStr(message, "No such file") != NULL)
6348         || (StrStr(message, "can't alloc") != NULL)
6349         || (StrStr(message, "Permission denied") != NULL)) {
6350
6351         cps->maybeThinking = FALSE;
6352         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6353                 cps->which, cps->program, cps->host, message);
6354         RemoveInputSource(cps->isr);
6355         DisplayFatalError(buf1, 0, 1);
6356         return;
6357     }
6358     
6359     /* 
6360      * Look for hint output
6361      */
6362     if (sscanf(message, "Hint: %s", buf1) == 1) {
6363         if (cps == &first && hintRequested) {
6364             hintRequested = FALSE;
6365             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6366                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6367                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6368                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6369                                     fromY, fromX, toY, toX, promoChar, buf1);
6370                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6371                 DisplayInformation(buf2);
6372             } else {
6373                 /* Hint move could not be parsed!? */
6374               snprintf(buf2, sizeof(buf2),
6375                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6376                         buf1, cps->which);
6377                 DisplayError(buf2, 0);
6378             }
6379         } else {
6380             strcpy(lastHint, buf1);
6381         }
6382         return;
6383     }
6384
6385     /*
6386      * Ignore other messages if game is not in progress
6387      */
6388     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6389         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6390
6391     /*
6392      * look for win, lose, draw, or draw offer
6393      */
6394     if (strncmp(message, "1-0", 3) == 0) {
6395         char *p, *q, *r = "";
6396         p = strchr(message, '{');
6397         if (p) {
6398             q = strchr(p, '}');
6399             if (q) {
6400                 *q = NULLCHAR;
6401                 r = p + 1;
6402             }
6403         }
6404         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6405         return;
6406     } else if (strncmp(message, "0-1", 3) == 0) {
6407         char *p, *q, *r = "";
6408         p = strchr(message, '{');
6409         if (p) {
6410             q = strchr(p, '}');
6411             if (q) {
6412                 *q = NULLCHAR;
6413                 r = p + 1;
6414             }
6415         }
6416         /* Kludge for Arasan 4.1 bug */
6417         if (strcmp(r, "Black resigns") == 0) {
6418             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6419             return;
6420         }
6421         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6422         return;
6423     } else if (strncmp(message, "1/2", 3) == 0) {
6424         char *p, *q, *r = "";
6425         p = strchr(message, '{');
6426         if (p) {
6427             q = strchr(p, '}');
6428             if (q) {
6429                 *q = NULLCHAR;
6430                 r = p + 1;
6431             }
6432         }
6433             
6434         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6435         return;
6436
6437     } else if (strncmp(message, "White resign", 12) == 0) {
6438         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6439         return;
6440     } else if (strncmp(message, "Black resign", 12) == 0) {
6441         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6442         return;
6443     } else if (strncmp(message, "White matches", 13) == 0 ||
6444                strncmp(message, "Black matches", 13) == 0   ) {
6445         /* [HGM] ignore GNUShogi noises */
6446         return;
6447     } else if (strncmp(message, "White", 5) == 0 &&
6448                message[5] != '(' &&
6449                StrStr(message, "Black") == NULL) {
6450         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6451         return;
6452     } else if (strncmp(message, "Black", 5) == 0 &&
6453                message[5] != '(') {
6454         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6455         return;
6456     } else if (strcmp(message, "resign") == 0 ||
6457                strcmp(message, "computer resigns") == 0) {
6458         switch (gameMode) {
6459           case MachinePlaysBlack:
6460           case IcsPlayingBlack:
6461             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6462             break;
6463           case MachinePlaysWhite:
6464           case IcsPlayingWhite:
6465             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6466             break;
6467           case TwoMachinesPlay:
6468             if (cps->twoMachinesColor[0] == 'w')
6469               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6470             else
6471               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6472             break;
6473           default:
6474             /* can't happen */
6475             break;
6476         }
6477         return;
6478     } else if (strncmp(message, "opponent mates", 14) == 0) {
6479         switch (gameMode) {
6480           case MachinePlaysBlack:
6481           case IcsPlayingBlack:
6482             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6483             break;
6484           case MachinePlaysWhite:
6485           case IcsPlayingWhite:
6486             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6487             break;
6488           case TwoMachinesPlay:
6489             if (cps->twoMachinesColor[0] == 'w')
6490               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6491             else
6492               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6493             break;
6494           default:
6495             /* can't happen */
6496             break;
6497         }
6498         return;
6499     } else if (strncmp(message, "computer mates", 14) == 0) {
6500         switch (gameMode) {
6501           case MachinePlaysBlack:
6502           case IcsPlayingBlack:
6503             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6504             break;
6505           case MachinePlaysWhite:
6506           case IcsPlayingWhite:
6507             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6508             break;
6509           case TwoMachinesPlay:
6510             if (cps->twoMachinesColor[0] == 'w')
6511               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6512             else
6513               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6514             break;
6515           default:
6516             /* can't happen */
6517             break;
6518         }
6519         return;
6520     } else if (strncmp(message, "checkmate", 9) == 0) {
6521         if (WhiteOnMove(forwardMostMove)) {
6522             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6523         } else {
6524             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6525         }
6526         return;
6527     } else if (strstr(message, "Draw") != NULL ||
6528                strstr(message, "game is a draw") != NULL) {
6529         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6530         return;
6531     } else if (strstr(message, "offer") != NULL &&
6532                strstr(message, "draw") != NULL) {
6533 #if ZIPPY
6534         if (appData.zippyPlay && first.initDone) {
6535             /* Relay offer to ICS */
6536             SendToICS(ics_prefix);
6537             SendToICS("draw\n");
6538         }
6539 #endif
6540         cps->offeredDraw = 2; /* valid until this engine moves twice */
6541         if (gameMode == TwoMachinesPlay) {
6542             if (cps->other->offeredDraw) {
6543                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6544             /* [HGM] in two-machine mode we delay relaying draw offer      */
6545             /* until after we also have move, to see if it is really claim */
6546             }
6547         } else if (gameMode == MachinePlaysWhite ||
6548                    gameMode == MachinePlaysBlack) {
6549           if (userOfferedDraw) {
6550             DisplayInformation(_("Machine accepts your draw offer"));
6551             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6552           } else {
6553             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6554           }
6555         }
6556     }
6557
6558     
6559     /*
6560      * Look for thinking output
6561      */
6562     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6563           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6564                                 ) {
6565         int plylev, mvleft, mvtot, curscore, time;
6566         char mvname[MOVE_LEN];
6567         u64 nodes; // [DM]
6568         char plyext;
6569         int ignore = FALSE;
6570         int prefixHint = FALSE;
6571         mvname[0] = NULLCHAR;
6572
6573         switch (gameMode) {
6574           case MachinePlaysBlack:
6575           case IcsPlayingBlack:
6576             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6577             break;
6578           case MachinePlaysWhite:
6579           case IcsPlayingWhite:
6580             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6581             break;
6582           case AnalyzeMode:
6583           case AnalyzeFile:
6584             break;
6585           case IcsObserving: /* [DM] icsEngineAnalyze */
6586             if (!appData.icsEngineAnalyze) ignore = TRUE;
6587             break;
6588           case TwoMachinesPlay:
6589             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6590                 ignore = TRUE;
6591             }
6592             break;
6593           default:
6594             ignore = TRUE;
6595             break;
6596         }
6597
6598         if (!ignore) {
6599             buf1[0] = NULLCHAR;
6600             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6601                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6602
6603                 if (plyext != ' ' && plyext != '\t') {
6604                     time *= 100;
6605                 }
6606
6607                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6608                 if( cps->scoreIsAbsolute && 
6609                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6610                 {
6611                     curscore = -curscore;
6612                 }
6613
6614
6615                 programStats.depth = plylev;
6616                 programStats.nodes = nodes;
6617                 programStats.time = time;
6618                 programStats.score = curscore;
6619                 programStats.got_only_move = 0;
6620
6621                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6622                         int ticklen;
6623
6624                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6625                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6626                         if(WhiteOnMove(forwardMostMove)) 
6627                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6628                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6629                 }
6630
6631                 /* Buffer overflow protection */
6632                 if (buf1[0] != NULLCHAR) {
6633                     if (strlen(buf1) >= sizeof(programStats.movelist)
6634                         && appData.debugMode) {
6635                         fprintf(debugFP,
6636                                 "PV is too long; using the first %d bytes.\n",
6637                                 sizeof(programStats.movelist) - 1);
6638                     }
6639
6640                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6641                 } else {
6642                     sprintf(programStats.movelist, " no PV\n");
6643                 }
6644
6645                 if (programStats.seen_stat) {
6646                     programStats.ok_to_send = 1;
6647                 }
6648
6649                 if (strchr(programStats.movelist, '(') != NULL) {
6650                     programStats.line_is_book = 1;
6651                     programStats.nr_moves = 0;
6652                     programStats.moves_left = 0;
6653                 } else {
6654                     programStats.line_is_book = 0;
6655                 }
6656
6657                 SendProgramStatsToFrontend( cps, &programStats );
6658
6659                 /* 
6660                     [AS] Protect the thinkOutput buffer from overflow... this
6661                     is only useful if buf1 hasn't overflowed first!
6662                 */
6663                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6664                         plylev, 
6665                         (gameMode == TwoMachinesPlay ?
6666                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6667                         ((double) curscore) / 100.0,
6668                         prefixHint ? lastHint : "",
6669                         prefixHint ? " " : "" );
6670
6671                 if( buf1[0] != NULLCHAR ) {
6672                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6673
6674                     if( strlen(buf1) > max_len ) {
6675                         if( appData.debugMode) {
6676                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6677                         }
6678                         buf1[max_len+1] = '\0';
6679                     }
6680
6681                     strcat( thinkOutput, buf1 );
6682                 }
6683
6684                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6685                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6686                     DisplayMove(currentMove - 1);
6687                     DisplayAnalysis();
6688                 }
6689                 return;
6690
6691             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6692                 /* crafty (9.25+) says "(only move) <move>"
6693                  * if there is only 1 legal move
6694                  */
6695                 sscanf(p, "(only move) %s", buf1);
6696                 sprintf(thinkOutput, "%s (only move)", buf1);
6697                 sprintf(programStats.movelist, "%s (only move)", buf1);
6698                 programStats.depth = 1;
6699                 programStats.nr_moves = 1;
6700                 programStats.moves_left = 1;
6701                 programStats.nodes = 1;
6702                 programStats.time = 1;
6703                 programStats.got_only_move = 1;
6704
6705                 /* Not really, but we also use this member to
6706                    mean "line isn't going to change" (Crafty
6707                    isn't searching, so stats won't change) */
6708                 programStats.line_is_book = 1;
6709
6710                 SendProgramStatsToFrontend( cps, &programStats );
6711                 
6712                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6713                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6714                     DisplayMove(currentMove - 1);
6715                     DisplayAnalysis();
6716                 }
6717                 return;
6718             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6719                               &time, &nodes, &plylev, &mvleft,
6720                               &mvtot, mvname) >= 5) {
6721                 /* The stat01: line is from Crafty (9.29+) in response
6722                    to the "." command */
6723                 programStats.seen_stat = 1;
6724                 cps->maybeThinking = TRUE;
6725
6726                 if (programStats.got_only_move || !appData.periodicUpdates)
6727                   return;
6728
6729                 programStats.depth = plylev;
6730                 programStats.time = time;
6731                 programStats.nodes = nodes;
6732                 programStats.moves_left = mvleft;
6733                 programStats.nr_moves = mvtot;
6734                 strcpy(programStats.move_name, mvname);
6735                 programStats.ok_to_send = 1;
6736                 programStats.movelist[0] = '\0';
6737
6738                 SendProgramStatsToFrontend( cps, &programStats );
6739
6740                 DisplayAnalysis();
6741                 return;
6742
6743             } else if (strncmp(message,"++",2) == 0) {
6744                 /* Crafty 9.29+ outputs this */
6745                 programStats.got_fail = 2;
6746                 return;
6747
6748             } else if (strncmp(message,"--",2) == 0) {
6749                 /* Crafty 9.29+ outputs this */
6750                 programStats.got_fail = 1;
6751                 return;
6752
6753             } else if (thinkOutput[0] != NULLCHAR &&
6754                        strncmp(message, "    ", 4) == 0) {
6755                 unsigned message_len;
6756
6757                 p = message;
6758                 while (*p && *p == ' ') p++;
6759
6760                 message_len = strlen( p );
6761
6762                 /* [AS] Avoid buffer overflow */
6763                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6764                     strcat(thinkOutput, " ");
6765                     strcat(thinkOutput, p);
6766                 }
6767
6768                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6769                     strcat(programStats.movelist, " ");
6770                     strcat(programStats.movelist, p);
6771                 }
6772
6773                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6774                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6775                     DisplayMove(currentMove - 1);
6776                     DisplayAnalysis();
6777                 }
6778                 return;
6779             }
6780         }
6781         else {
6782             buf1[0] = NULLCHAR;
6783
6784             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6785                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6786             {
6787                 ChessProgramStats cpstats;
6788
6789                 if (plyext != ' ' && plyext != '\t') {
6790                     time *= 100;
6791                 }
6792
6793                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6794                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6795                     curscore = -curscore;
6796                 }
6797
6798                 cpstats.depth = plylev;
6799                 cpstats.nodes = nodes;
6800                 cpstats.time = time;
6801                 cpstats.score = curscore;
6802                 cpstats.got_only_move = 0;
6803                 cpstats.movelist[0] = '\0';
6804
6805                 if (buf1[0] != NULLCHAR) {
6806                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6807                 }
6808
6809                 cpstats.ok_to_send = 0;
6810                 cpstats.line_is_book = 0;
6811                 cpstats.nr_moves = 0;
6812                 cpstats.moves_left = 0;
6813
6814                 SendProgramStatsToFrontend( cps, &cpstats );
6815             }
6816         }
6817     }
6818 }
6819
6820
6821 /* Parse a game score from the character string "game", and
6822    record it as the history of the current game.  The game
6823    score is NOT assumed to start from the standard position. 
6824    The display is not updated in any way.
6825    */
6826 void
6827 ParseGameHistory(game)
6828      char *game;
6829 {
6830     ChessMove moveType;
6831     int fromX, fromY, toX, toY, boardIndex;
6832     char promoChar;
6833     char *p, *q;
6834     char buf[MSG_SIZ];
6835
6836     if (appData.debugMode)
6837       fprintf(debugFP, "Parsing game history: %s\n", game);
6838
6839     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6840     gameInfo.site = StrSave(appData.icsHost);
6841     gameInfo.date = PGNDate();
6842     gameInfo.round = StrSave("-");
6843
6844     /* Parse out names of players */
6845     while (*game == ' ') game++;
6846     p = buf;
6847     while (*game != ' ') *p++ = *game++;
6848     *p = NULLCHAR;
6849     gameInfo.white = StrSave(buf);
6850     while (*game == ' ') game++;
6851     p = buf;
6852     while (*game != ' ' && *game != '\n') *p++ = *game++;
6853     *p = NULLCHAR;
6854     gameInfo.black = StrSave(buf);
6855
6856     /* Parse moves */
6857     boardIndex = blackPlaysFirst ? 1 : 0;
6858     yynewstr(game);
6859     for (;;) {
6860         yyboardindex = boardIndex;
6861         moveType = (ChessMove) yylex();
6862         switch (moveType) {
6863           case IllegalMove:             /* maybe suicide chess, etc. */
6864   if (appData.debugMode) {
6865     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6866     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6867     setbuf(debugFP, NULL);
6868   }
6869           case WhitePromotionChancellor:
6870           case BlackPromotionChancellor:
6871           case WhitePromotionArchbishop:
6872           case BlackPromotionArchbishop:
6873           case WhitePromotionQueen:
6874           case BlackPromotionQueen:
6875           case WhitePromotionRook:
6876           case BlackPromotionRook:
6877           case WhitePromotionBishop:
6878           case BlackPromotionBishop:
6879           case WhitePromotionKnight:
6880           case BlackPromotionKnight:
6881           case WhitePromotionKing:
6882           case BlackPromotionKing:
6883           case NormalMove:
6884           case WhiteCapturesEnPassant:
6885           case BlackCapturesEnPassant:
6886           case WhiteKingSideCastle:
6887           case WhiteQueenSideCastle:
6888           case BlackKingSideCastle:
6889           case BlackQueenSideCastle:
6890           case WhiteKingSideCastleWild:
6891           case WhiteQueenSideCastleWild:
6892           case BlackKingSideCastleWild:
6893           case BlackQueenSideCastleWild:
6894           /* PUSH Fabien */
6895           case WhiteHSideCastleFR:
6896           case WhiteASideCastleFR:
6897           case BlackHSideCastleFR:
6898           case BlackASideCastleFR:
6899           /* POP Fabien */
6900             fromX = currentMoveString[0] - AAA;
6901             fromY = currentMoveString[1] - ONE;
6902             toX = currentMoveString[2] - AAA;
6903             toY = currentMoveString[3] - ONE;
6904             promoChar = currentMoveString[4];
6905             break;
6906           case WhiteDrop:
6907           case BlackDrop:
6908             fromX = moveType == WhiteDrop ?
6909               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6910             (int) CharToPiece(ToLower(currentMoveString[0]));
6911             fromY = DROP_RANK;
6912             toX = currentMoveString[2] - AAA;
6913             toY = currentMoveString[3] - ONE;
6914             promoChar = NULLCHAR;
6915             break;
6916           case AmbiguousMove:
6917             /* bug? */
6918             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6919   if (appData.debugMode) {
6920     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6921     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6922     setbuf(debugFP, NULL);
6923   }
6924             DisplayError(buf, 0);
6925             return;
6926           case ImpossibleMove:
6927             /* bug? */
6928             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6929   if (appData.debugMode) {
6930     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6931     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6932     setbuf(debugFP, NULL);
6933   }
6934             DisplayError(buf, 0);
6935             return;
6936           case (ChessMove) 0:   /* end of file */
6937             if (boardIndex < backwardMostMove) {
6938                 /* Oops, gap.  How did that happen? */
6939                 DisplayError(_("Gap in move list"), 0);
6940                 return;
6941             }
6942             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6943             if (boardIndex > forwardMostMove) {
6944                 forwardMostMove = boardIndex;
6945             }
6946             return;
6947           case ElapsedTime:
6948             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6949                 strcat(parseList[boardIndex-1], " ");
6950                 strcat(parseList[boardIndex-1], yy_text);
6951             }
6952             continue;
6953           case Comment:
6954           case PGNTag:
6955           case NAG:
6956           default:
6957             /* ignore */
6958             continue;
6959           case WhiteWins:
6960           case BlackWins:
6961           case GameIsDrawn:
6962           case GameUnfinished:
6963             if (gameMode == IcsExamining) {
6964                 if (boardIndex < backwardMostMove) {
6965                     /* Oops, gap.  How did that happen? */
6966                     return;
6967                 }
6968                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6969                 return;
6970             }
6971             gameInfo.result = moveType;
6972             p = strchr(yy_text, '{');
6973             if (p == NULL) p = strchr(yy_text, '(');
6974             if (p == NULL) {
6975                 p = yy_text;
6976                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6977             } else {
6978                 q = strchr(p, *p == '{' ? '}' : ')');
6979                 if (q != NULL) *q = NULLCHAR;
6980                 p++;
6981             }
6982             gameInfo.resultDetails = StrSave(p);
6983             continue;
6984         }
6985         if (boardIndex >= forwardMostMove &&
6986             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6987             backwardMostMove = blackPlaysFirst ? 1 : 0;
6988             return;
6989         }
6990         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6991                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6992                                  parseList[boardIndex]);
6993         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6994         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6995         /* currentMoveString is set as a side-effect of yylex */
6996         strcpy(moveList[boardIndex], currentMoveString);
6997         strcat(moveList[boardIndex], "\n");
6998         boardIndex++;
6999         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7000                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7001         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7002                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7003           case MT_NONE:
7004           case MT_STALEMATE:
7005           default:
7006             break;
7007           case MT_CHECK:
7008             if(gameInfo.variant != VariantShogi)
7009                 strcat(parseList[boardIndex - 1], "+");
7010             break;
7011           case MT_CHECKMATE:
7012           case MT_STAINMATE:
7013             strcat(parseList[boardIndex - 1], "#");
7014             break;
7015         }
7016     }
7017 }
7018
7019
7020 /* Apply a move to the given board  */
7021 void
7022 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7023      int fromX, fromY, toX, toY;
7024      int promoChar;
7025      Board board;
7026      char *castling;
7027      char *ep;
7028 {
7029   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7030
7031     /* [HGM] compute & store e.p. status and castling rights for new position */
7032     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7033     { int i;
7034
7035       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7036       oldEP = *ep;
7037       *ep = EP_NONE;
7038
7039       if( board[toY][toX] != EmptySquare ) 
7040            *ep = EP_CAPTURE;  
7041
7042       if( board[fromY][fromX] == WhitePawn ) {
7043            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7044                *ep = EP_PAWN_MOVE;
7045            if( toY-fromY==2) {
7046                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7047                         gameInfo.variant != VariantBerolina || toX < fromX)
7048                       *ep = toX | berolina;
7049                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7050                         gameInfo.variant != VariantBerolina || toX > fromX) 
7051                       *ep = toX;
7052            }
7053       } else 
7054       if( board[fromY][fromX] == BlackPawn ) {
7055            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7056                *ep = EP_PAWN_MOVE; 
7057            if( toY-fromY== -2) {
7058                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7059                         gameInfo.variant != VariantBerolina || toX < fromX)
7060                       *ep = toX | berolina;
7061                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7062                         gameInfo.variant != VariantBerolina || toX > fromX) 
7063                       *ep = toX;
7064            }
7065        }
7066
7067        for(i=0; i<nrCastlingRights; i++) {
7068            if(castling[i] == fromX && castlingRank[i] == fromY ||
7069               castling[i] == toX   && castlingRank[i] == toY   
7070              ) castling[i] = -1; // revoke for moved or captured piece
7071        }
7072
7073     }
7074
7075   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7076   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7077        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7078          
7079   if (fromX == toX && fromY == toY) return;
7080
7081   if (fromY == DROP_RANK) {
7082         /* must be first */
7083         piece = board[toY][toX] = (ChessSquare) fromX;
7084   } else {
7085      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7086      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7087      if(gameInfo.variant == VariantKnightmate)
7088          king += (int) WhiteUnicorn - (int) WhiteKing;
7089
7090     /* Code added by Tord: */
7091     /* FRC castling assumed when king captures friendly rook. */
7092     if (board[fromY][fromX] == WhiteKing &&
7093              board[toY][toX] == WhiteRook) {
7094       board[fromY][fromX] = EmptySquare;
7095       board[toY][toX] = EmptySquare;
7096       if(toX > fromX) {
7097         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7098       } else {
7099         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7100       }
7101     } else if (board[fromY][fromX] == BlackKing &&
7102                board[toY][toX] == BlackRook) {
7103       board[fromY][fromX] = EmptySquare;
7104       board[toY][toX] = EmptySquare;
7105       if(toX > fromX) {
7106         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7107       } else {
7108         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7109       }
7110     /* End of code added by Tord */
7111
7112     } else if (board[fromY][fromX] == king
7113         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7114         && toY == fromY && toX > fromX+1) {
7115         board[fromY][fromX] = EmptySquare;
7116         board[toY][toX] = king;
7117         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7118         board[fromY][BOARD_RGHT-1] = EmptySquare;
7119     } else if (board[fromY][fromX] == king
7120         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7121                && toY == fromY && toX < fromX-1) {
7122         board[fromY][fromX] = EmptySquare;
7123         board[toY][toX] = king;
7124         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7125         board[fromY][BOARD_LEFT] = EmptySquare;
7126     } else if (board[fromY][fromX] == WhitePawn
7127                && toY == BOARD_HEIGHT-1
7128                && gameInfo.variant != VariantXiangqi
7129                ) {
7130         /* white pawn promotion */
7131         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7132         if (board[toY][toX] == EmptySquare) {
7133             board[toY][toX] = WhiteQueen;
7134         }
7135         if(gameInfo.variant==VariantBughouse ||
7136            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7137             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7138         board[fromY][fromX] = EmptySquare;
7139     } else if ((fromY == BOARD_HEIGHT-4)
7140                && (toX != fromX)
7141                && gameInfo.variant != VariantXiangqi
7142                && gameInfo.variant != VariantBerolina
7143                && (board[fromY][fromX] == WhitePawn)
7144                && (board[toY][toX] == EmptySquare)) {
7145         board[fromY][fromX] = EmptySquare;
7146         board[toY][toX] = WhitePawn;
7147         captured = board[toY - 1][toX];
7148         board[toY - 1][toX] = EmptySquare;
7149     } else if ((fromY == BOARD_HEIGHT-4)
7150                && (toX == fromX)
7151                && gameInfo.variant == VariantBerolina
7152                && (board[fromY][fromX] == WhitePawn)
7153                && (board[toY][toX] == EmptySquare)) {
7154         board[fromY][fromX] = EmptySquare;
7155         board[toY][toX] = WhitePawn;
7156         if(oldEP & EP_BEROLIN_A) {
7157                 captured = board[fromY][fromX-1];
7158                 board[fromY][fromX-1] = EmptySquare;
7159         }else{  captured = board[fromY][fromX+1];
7160                 board[fromY][fromX+1] = EmptySquare;
7161         }
7162     } else if (board[fromY][fromX] == king
7163         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7164                && toY == fromY && toX > fromX+1) {
7165         board[fromY][fromX] = EmptySquare;
7166         board[toY][toX] = king;
7167         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7168         board[fromY][BOARD_RGHT-1] = EmptySquare;
7169     } else if (board[fromY][fromX] == king
7170         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7171                && toY == fromY && toX < fromX-1) {
7172         board[fromY][fromX] = EmptySquare;
7173         board[toY][toX] = king;
7174         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7175         board[fromY][BOARD_LEFT] = EmptySquare;
7176     } else if (fromY == 7 && fromX == 3
7177                && board[fromY][fromX] == BlackKing
7178                && toY == 7 && toX == 5) {
7179         board[fromY][fromX] = EmptySquare;
7180         board[toY][toX] = BlackKing;
7181         board[fromY][7] = EmptySquare;
7182         board[toY][4] = BlackRook;
7183     } else if (fromY == 7 && fromX == 3
7184                && board[fromY][fromX] == BlackKing
7185                && toY == 7 && toX == 1) {
7186         board[fromY][fromX] = EmptySquare;
7187         board[toY][toX] = BlackKing;
7188         board[fromY][0] = EmptySquare;
7189         board[toY][2] = BlackRook;
7190     } else if (board[fromY][fromX] == BlackPawn
7191                && toY == 0
7192                && gameInfo.variant != VariantXiangqi
7193                ) {
7194         /* black pawn promotion */
7195         board[0][toX] = CharToPiece(ToLower(promoChar));
7196         if (board[0][toX] == EmptySquare) {
7197             board[0][toX] = BlackQueen;
7198         }
7199         if(gameInfo.variant==VariantBughouse ||
7200            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7201             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7202         board[fromY][fromX] = EmptySquare;
7203     } else if ((fromY == 3)
7204                && (toX != fromX)
7205                && gameInfo.variant != VariantXiangqi
7206                && gameInfo.variant != VariantBerolina
7207                && (board[fromY][fromX] == BlackPawn)
7208                && (board[toY][toX] == EmptySquare)) {
7209         board[fromY][fromX] = EmptySquare;
7210         board[toY][toX] = BlackPawn;
7211         captured = board[toY + 1][toX];
7212         board[toY + 1][toX] = EmptySquare;
7213     } else if ((fromY == 3)
7214                && (toX == fromX)
7215                && gameInfo.variant == VariantBerolina
7216                && (board[fromY][fromX] == BlackPawn)
7217                && (board[toY][toX] == EmptySquare)) {
7218         board[fromY][fromX] = EmptySquare;
7219         board[toY][toX] = BlackPawn;
7220         if(oldEP & EP_BEROLIN_A) {
7221                 captured = board[fromY][fromX-1];
7222                 board[fromY][fromX-1] = EmptySquare;
7223         }else{  captured = board[fromY][fromX+1];
7224                 board[fromY][fromX+1] = EmptySquare;
7225         }
7226     } else {
7227         board[toY][toX] = board[fromY][fromX];
7228         board[fromY][fromX] = EmptySquare;
7229     }
7230
7231     /* [HGM] now we promote for Shogi, if needed */
7232     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7233         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7234   }
7235
7236     if (gameInfo.holdingsWidth != 0) {
7237
7238       /* !!A lot more code needs to be written to support holdings  */
7239       /* [HGM] OK, so I have written it. Holdings are stored in the */
7240       /* penultimate board files, so they are automaticlly stored   */
7241       /* in the game history.                                       */
7242       if (fromY == DROP_RANK) {
7243         /* Delete from holdings, by decreasing count */
7244         /* and erasing image if necessary            */
7245         p = (int) fromX;
7246         if(p < (int) BlackPawn) { /* white drop */
7247              p -= (int)WhitePawn;
7248              if(p >= gameInfo.holdingsSize) p = 0;
7249              if(--board[p][BOARD_WIDTH-2] == 0)
7250                   board[p][BOARD_WIDTH-1] = EmptySquare;
7251         } else {                  /* black drop */
7252              p -= (int)BlackPawn;
7253              if(p >= gameInfo.holdingsSize) p = 0;
7254              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7255                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7256         }
7257       }
7258       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7259           && gameInfo.variant != VariantBughouse        ) {
7260         /* [HGM] holdings: Add to holdings, if holdings exist */
7261         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7262                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7263                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7264         }
7265         p = (int) captured;
7266         if (p >= (int) BlackPawn) {
7267           p -= (int)BlackPawn;
7268           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7269                   /* in Shogi restore piece to its original  first */
7270                   captured = (ChessSquare) (DEMOTED captured);
7271                   p = DEMOTED p;
7272           }
7273           p = PieceToNumber((ChessSquare)p);
7274           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7275           board[p][BOARD_WIDTH-2]++;
7276           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7277         } else {
7278           p -= (int)WhitePawn;
7279           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7280                   captured = (ChessSquare) (DEMOTED captured);
7281                   p = DEMOTED p;
7282           }
7283           p = PieceToNumber((ChessSquare)p);
7284           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7285           board[BOARD_HEIGHT-1-p][1]++;
7286           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7287         }
7288       }
7289
7290     } else if (gameInfo.variant == VariantAtomic) {
7291       if (captured != EmptySquare) {
7292         int y, x;
7293         for (y = toY-1; y <= toY+1; y++) {
7294           for (x = toX-1; x <= toX+1; x++) {
7295             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7296                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7297               board[y][x] = EmptySquare;
7298             }
7299           }
7300         }
7301         board[toY][toX] = EmptySquare;
7302       }
7303     }
7304     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7305         /* [HGM] Shogi promotions */
7306         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7307     }
7308
7309     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7310                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7311         // [HGM] superchess: take promotion piece out of holdings
7312         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7313         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7314             if(!--board[k][BOARD_WIDTH-2])
7315                 board[k][BOARD_WIDTH-1] = EmptySquare;
7316         } else {
7317             if(!--board[BOARD_HEIGHT-1-k][1])
7318                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7319         }
7320     }
7321
7322 }
7323
7324 /* Updates forwardMostMove */
7325 void
7326 MakeMove(fromX, fromY, toX, toY, promoChar)
7327      int fromX, fromY, toX, toY;
7328      int promoChar;
7329 {
7330 //    forwardMostMove++; // [HGM] bare: moved downstream
7331
7332     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7333         int timeLeft; static int lastLoadFlag=0; int king, piece;
7334         piece = boards[forwardMostMove][fromY][fromX];
7335         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7336         if(gameInfo.variant == VariantKnightmate)
7337             king += (int) WhiteUnicorn - (int) WhiteKing;
7338         if(forwardMostMove == 0) {
7339             if(blackPlaysFirst) 
7340                 fprintf(serverMoves, "%s;", second.tidy);
7341             fprintf(serverMoves, "%s;", first.tidy);
7342             if(!blackPlaysFirst) 
7343                 fprintf(serverMoves, "%s;", second.tidy);
7344         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7345         lastLoadFlag = loadFlag;
7346         // print base move
7347         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7348         // print castling suffix
7349         if( toY == fromY && piece == king ) {
7350             if(toX-fromX > 1)
7351                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7352             if(fromX-toX >1)
7353                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7354         }
7355         // e.p. suffix
7356         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7357              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7358              boards[forwardMostMove][toY][toX] == EmptySquare
7359              && fromX != toX )
7360                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7361         // promotion suffix
7362         if(promoChar != NULLCHAR)
7363                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7364         if(!loadFlag) {
7365             fprintf(serverMoves, "/%d/%d",
7366                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7367             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7368             else                      timeLeft = blackTimeRemaining/1000;
7369             fprintf(serverMoves, "/%d", timeLeft);
7370         }
7371         fflush(serverMoves);
7372     }
7373
7374     if (forwardMostMove+1 >= MAX_MOVES) {
7375       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7376                         0, 1);
7377       return;
7378     }
7379     if (commentList[forwardMostMove+1] != NULL) {
7380         free(commentList[forwardMostMove+1]);
7381         commentList[forwardMostMove+1] = NULL;
7382     }
7383     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7384     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7385     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7386                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7387     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7388     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7389     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7390     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7391     gameInfo.result = GameUnfinished;
7392     if (gameInfo.resultDetails != NULL) {
7393         free(gameInfo.resultDetails);
7394         gameInfo.resultDetails = NULL;
7395     }
7396     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7397                               moveList[forwardMostMove - 1]);
7398     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7399                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7400                              fromY, fromX, toY, toX, promoChar,
7401                              parseList[forwardMostMove - 1]);
7402     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7403                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7404                             castlingRights[forwardMostMove]) ) {
7405       case MT_NONE:
7406       case MT_STALEMATE:
7407       default:
7408         break;
7409       case MT_CHECK:
7410         if(gameInfo.variant != VariantShogi)
7411             strcat(parseList[forwardMostMove - 1], "+");
7412         break;
7413       case MT_CHECKMATE:
7414       case MT_STAINMATE:
7415         strcat(parseList[forwardMostMove - 1], "#");
7416         break;
7417     }
7418     if (appData.debugMode) {
7419         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7420     }
7421
7422 }
7423
7424 /* Updates currentMove if not pausing */
7425 void
7426 ShowMove(fromX, fromY, toX, toY)
7427 {
7428     int instant = (gameMode == PlayFromGameFile) ?
7429         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7430     if(appData.noGUI) return;
7431     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7432         if (!instant) {
7433             if (forwardMostMove == currentMove + 1) {
7434                 AnimateMove(boards[forwardMostMove - 1],
7435                             fromX, fromY, toX, toY);
7436             }
7437             if (appData.highlightLastMove) {
7438                 SetHighlights(fromX, fromY, toX, toY);
7439             }
7440         }
7441         currentMove = forwardMostMove;
7442     }
7443
7444     if (instant) return;
7445
7446     DisplayMove(currentMove - 1);
7447     DrawPosition(FALSE, boards[currentMove]);
7448     DisplayBothClocks();
7449     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7450 }
7451
7452 void SendEgtPath(ChessProgramState *cps)
7453 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7454         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7455
7456         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7457
7458         while(*p) {
7459             char c, *q = name+1, *r, *s;
7460
7461             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7462             while(*p && *p != ',') *q++ = *p++;
7463             *q++ = ':'; *q = 0;
7464             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7465                 strcmp(name, ",nalimov:") == 0 ) {
7466                 // take nalimov path from the menu-changeable option first, if it is defined
7467                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7468                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7469             } else
7470             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7471                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7472                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7473                 s = r = StrStr(s, ":") + 1; // beginning of path info
7474                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7475                 c = *r; *r = 0;             // temporarily null-terminate path info
7476                     *--q = 0;               // strip of trailig ':' from name
7477                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7478                 *r = c;
7479                 SendToProgram(buf,cps);     // send egtbpath command for this format
7480             }
7481             if(*p == ',') p++; // read away comma to position for next format name
7482         }
7483 }
7484
7485 void
7486 InitChessProgram(cps, setup)
7487      ChessProgramState *cps;
7488      int setup; /* [HGM] needed to setup FRC opening position */
7489 {
7490     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7491     if (appData.noChessProgram) return;
7492     hintRequested = FALSE;
7493     bookRequested = FALSE;
7494
7495     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7496     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7497     if(cps->memSize) { /* [HGM] memory */
7498         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7499         SendToProgram(buf, cps);
7500     }
7501     SendEgtPath(cps); /* [HGM] EGT */
7502     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7503         sprintf(buf, "cores %d\n", appData.smpCores);
7504         SendToProgram(buf, cps);
7505     }
7506
7507     SendToProgram(cps->initString, cps);
7508     if (gameInfo.variant != VariantNormal &&
7509         gameInfo.variant != VariantLoadable
7510         /* [HGM] also send variant if board size non-standard */
7511         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7512                                             ) {
7513       char *v = VariantName(gameInfo.variant);
7514       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7515         /* [HGM] in protocol 1 we have to assume all variants valid */
7516         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7517         DisplayFatalError(buf, 0, 1);
7518         return;
7519       }
7520
7521       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7522       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7523       if( gameInfo.variant == VariantXiangqi )
7524            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7525       if( gameInfo.variant == VariantShogi )
7526            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7527       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7528            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7529       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7530                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7531            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7532       if( gameInfo.variant == VariantCourier )
7533            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7534       if( gameInfo.variant == VariantSuper )
7535            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7536       if( gameInfo.variant == VariantGreat )
7537            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7538
7539       if(overruled) {
7540            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7541                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7542            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7543            if(StrStr(cps->variants, b) == NULL) { 
7544                // specific sized variant not known, check if general sizing allowed
7545                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7546                    if(StrStr(cps->variants, "boardsize") == NULL) {
7547                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7548                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7549                        DisplayFatalError(buf, 0, 1);
7550                        return;
7551                    }
7552                    /* [HGM] here we really should compare with the maximum supported board size */
7553                }
7554            }
7555       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7556       sprintf(buf, "variant %s\n", b);
7557       SendToProgram(buf, cps);
7558     }
7559     currentlyInitializedVariant = gameInfo.variant;
7560
7561     /* [HGM] send opening position in FRC to first engine */
7562     if(setup) {
7563           SendToProgram("force\n", cps);
7564           SendBoard(cps, 0);
7565           /* engine is now in force mode! Set flag to wake it up after first move. */
7566           setboardSpoiledMachineBlack = 1;
7567     }
7568
7569     if (cps->sendICS) {
7570       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7571       SendToProgram(buf, cps);
7572     }
7573     cps->maybeThinking = FALSE;
7574     cps->offeredDraw = 0;
7575     if (!appData.icsActive) {
7576         SendTimeControl(cps, movesPerSession, timeControl,
7577                         timeIncrement, appData.searchDepth,
7578                         searchTime);
7579     }
7580     if (appData.showThinking 
7581         // [HGM] thinking: four options require thinking output to be sent
7582         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7583                                 ) {
7584         SendToProgram("post\n", cps);
7585     }
7586     SendToProgram("hard\n", cps);
7587     if (!appData.ponderNextMove) {
7588         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7589            it without being sure what state we are in first.  "hard"
7590            is not a toggle, so that one is OK.
7591          */
7592         SendToProgram("easy\n", cps);
7593     }
7594     if (cps->usePing) {
7595       sprintf(buf, "ping %d\n", ++cps->lastPing);
7596       SendToProgram(buf, cps);
7597     }
7598     cps->initDone = TRUE;
7599 }   
7600
7601
7602 void
7603 StartChessProgram(cps)
7604      ChessProgramState *cps;
7605 {
7606     char buf[MSG_SIZ];
7607     int err;
7608
7609     if (appData.noChessProgram) return;
7610     cps->initDone = FALSE;
7611
7612     if (strcmp(cps->host, "localhost") == 0) {
7613         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7614     } else if (*appData.remoteShell == NULLCHAR) {
7615         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7616     } else {
7617         if (*appData.remoteUser == NULLCHAR) {
7618           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7619                     cps->program);
7620         } else {
7621           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7622                     cps->host, appData.remoteUser, cps->program);
7623         }
7624         err = StartChildProcess(buf, "", &cps->pr);
7625     }
7626     
7627     if (err != 0) {
7628         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7629         DisplayFatalError(buf, err, 1);
7630         cps->pr = NoProc;
7631         cps->isr = NULL;
7632         return;
7633     }
7634     
7635     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7636     if (cps->protocolVersion > 1) {
7637       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7638       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7639       cps->comboCnt = 0;  //                and values of combo boxes
7640       SendToProgram(buf, cps);
7641     } else {
7642       SendToProgram("xboard\n", cps);
7643     }
7644 }
7645
7646
7647 void
7648 TwoMachinesEventIfReady P((void))
7649 {
7650   if (first.lastPing != first.lastPong) {
7651     DisplayMessage("", _("Waiting for first chess program"));
7652     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7653     return;
7654   }
7655   if (second.lastPing != second.lastPong) {
7656     DisplayMessage("", _("Waiting for second chess program"));
7657     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7658     return;
7659   }
7660   ThawUI();
7661   TwoMachinesEvent();
7662 }
7663
7664 void
7665 NextMatchGame P((void))
7666 {
7667     int index; /* [HGM] autoinc: step lod index during match */
7668     Reset(FALSE, TRUE);
7669     if (*appData.loadGameFile != NULLCHAR) {
7670         index = appData.loadGameIndex;
7671         if(index < 0) { // [HGM] autoinc
7672             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7673             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7674         } 
7675         LoadGameFromFile(appData.loadGameFile,
7676                          index,
7677                          appData.loadGameFile, FALSE);
7678     } else if (*appData.loadPositionFile != NULLCHAR) {
7679         index = appData.loadPositionIndex;
7680         if(index < 0) { // [HGM] autoinc
7681             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7682             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7683         } 
7684         LoadPositionFromFile(appData.loadPositionFile,
7685                              index,
7686                              appData.loadPositionFile);
7687     }
7688     TwoMachinesEventIfReady();
7689 }
7690
7691 void UserAdjudicationEvent( int result )
7692 {
7693     ChessMove gameResult = GameIsDrawn;
7694
7695     if( result > 0 ) {
7696         gameResult = WhiteWins;
7697     }
7698     else if( result < 0 ) {
7699         gameResult = BlackWins;
7700     }
7701
7702     if( gameMode == TwoMachinesPlay ) {
7703         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7704     }
7705 }
7706
7707
7708 // [HGM] save: calculate checksum of game to make games easily identifiable
7709 int StringCheckSum(char *s)
7710 {
7711         int i = 0;
7712         if(s==NULL) return 0;
7713         while(*s) i = i*259 + *s++;
7714         return i;
7715 }
7716
7717 int GameCheckSum()
7718 {
7719         int i, sum=0;
7720         for(i=backwardMostMove; i<forwardMostMove; i++) {
7721                 sum += pvInfoList[i].depth;
7722                 sum += StringCheckSum(parseList[i]);
7723                 sum += StringCheckSum(commentList[i]);
7724                 sum *= 261;
7725         }
7726         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7727         return sum + StringCheckSum(commentList[i]);
7728 } // end of save patch
7729
7730 void
7731 GameEnds(result, resultDetails, whosays)
7732      ChessMove result;
7733      char *resultDetails;
7734      int whosays;
7735 {
7736     GameMode nextGameMode;
7737     int isIcsGame;
7738     char buf[MSG_SIZ];
7739
7740     if(endingGame) return; /* [HGM] crash: forbid recursion */
7741     endingGame = 1;
7742
7743     if (appData.debugMode) {
7744       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7745               result, resultDetails ? resultDetails : "(null)", whosays);
7746     }
7747
7748     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7749         /* If we are playing on ICS, the server decides when the
7750            game is over, but the engine can offer to draw, claim 
7751            a draw, or resign. 
7752          */
7753 #if ZIPPY
7754         if (appData.zippyPlay && first.initDone) {
7755             if (result == GameIsDrawn) {
7756                 /* In case draw still needs to be claimed */
7757                 SendToICS(ics_prefix);
7758                 SendToICS("draw\n");
7759             } else if (StrCaseStr(resultDetails, "resign")) {
7760                 SendToICS(ics_prefix);
7761                 SendToICS("resign\n");
7762             }
7763         }
7764 #endif
7765         endingGame = 0; /* [HGM] crash */
7766         return;
7767     }
7768
7769     /* If we're loading the game from a file, stop */
7770     if (whosays == GE_FILE) {
7771       (void) StopLoadGameTimer();
7772       gameFileFP = NULL;
7773     }
7774
7775     /* Cancel draw offers */
7776     first.offeredDraw = second.offeredDraw = 0;
7777
7778     /* If this is an ICS game, only ICS can really say it's done;
7779        if not, anyone can. */
7780     isIcsGame = (gameMode == IcsPlayingWhite || 
7781                  gameMode == IcsPlayingBlack || 
7782                  gameMode == IcsObserving    || 
7783                  gameMode == IcsExamining);
7784
7785     if (!isIcsGame || whosays == GE_ICS) {
7786         /* OK -- not an ICS game, or ICS said it was done */
7787         StopClocks();
7788         if (!isIcsGame && !appData.noChessProgram) 
7789           SetUserThinkingEnables();
7790     
7791         /* [HGM] if a machine claims the game end we verify this claim */
7792         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7793             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7794                 char claimer;
7795                 ChessMove trueResult = (ChessMove) -1;
7796
7797                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7798                                             first.twoMachinesColor[0] :
7799                                             second.twoMachinesColor[0] ;
7800
7801                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7802                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7803                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7804                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7805                 } else
7806                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7807                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7808                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7809                 } else
7810                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7811                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7812                 }
7813
7814                 // now verify win claims, but not in drop games, as we don't understand those yet
7815                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7816                                                  || gameInfo.variant == VariantGreat) &&
7817                     (result == WhiteWins && claimer == 'w' ||
7818                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7819                       if (appData.debugMode) {
7820                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7821                                 result, epStatus[forwardMostMove], forwardMostMove);
7822                       }
7823                       if(result != trueResult) {
7824                               sprintf(buf, "False win claim: '%s'", resultDetails);
7825                               result = claimer == 'w' ? BlackWins : WhiteWins;
7826                               resultDetails = buf;
7827                       }
7828                 } else
7829                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7830                     && (forwardMostMove <= backwardMostMove ||
7831                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7832                         (claimer=='b')==(forwardMostMove&1))
7833                                                                                   ) {
7834                       /* [HGM] verify: draws that were not flagged are false claims */
7835                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7836                       result = claimer == 'w' ? BlackWins : WhiteWins;
7837                       resultDetails = buf;
7838                 }
7839                 /* (Claiming a loss is accepted no questions asked!) */
7840             }
7841             /* [HGM] bare: don't allow bare King to win */
7842             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7843                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7844                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7845                && result != GameIsDrawn)
7846             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7847                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7848                         int p = (int)boards[forwardMostMove][i][j] - color;
7849                         if(p >= 0 && p <= (int)WhiteKing) k++;
7850                 }
7851                 if (appData.debugMode) {
7852                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7853                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7854                 }
7855                 if(k <= 1) {
7856                         result = GameIsDrawn;
7857                         sprintf(buf, "%s but bare king", resultDetails);
7858                         resultDetails = buf;
7859                 }
7860             }
7861         }
7862
7863
7864         if(serverMoves != NULL && !loadFlag) { char c = '=';
7865             if(result==WhiteWins) c = '+';
7866             if(result==BlackWins) c = '-';
7867             if(resultDetails != NULL)
7868                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7869         }
7870         if (resultDetails != NULL) {
7871             gameInfo.result = result;
7872             gameInfo.resultDetails = StrSave(resultDetails);
7873
7874             /* display last move only if game was not loaded from file */
7875             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7876                 DisplayMove(currentMove - 1);
7877     
7878             if (forwardMostMove != 0) {
7879                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7880                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7881                                                                 ) {
7882                     if (*appData.saveGameFile != NULLCHAR) {
7883                         SaveGameToFile(appData.saveGameFile, TRUE);
7884                     } else if (appData.autoSaveGames) {
7885                         AutoSaveGame();
7886                     }
7887                     if (*appData.savePositionFile != NULLCHAR) {
7888                         SavePositionToFile(appData.savePositionFile);
7889                     }
7890                 }
7891             }
7892
7893             /* Tell program how game ended in case it is learning */
7894             /* [HGM] Moved this to after saving the PGN, just in case */
7895             /* engine died and we got here through time loss. In that */
7896             /* case we will get a fatal error writing the pipe, which */
7897             /* would otherwise lose us the PGN.                       */
7898             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7899             /* output during GameEnds should never be fatal anymore   */
7900             if (gameMode == MachinePlaysWhite ||
7901                 gameMode == MachinePlaysBlack ||
7902                 gameMode == TwoMachinesPlay ||
7903                 gameMode == IcsPlayingWhite ||
7904                 gameMode == IcsPlayingBlack ||
7905                 gameMode == BeginningOfGame) {
7906                 char buf[MSG_SIZ];
7907                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7908                         resultDetails);
7909                 if (first.pr != NoProc) {
7910                     SendToProgram(buf, &first);
7911                 }
7912                 if (second.pr != NoProc &&
7913                     gameMode == TwoMachinesPlay) {
7914                     SendToProgram(buf, &second);
7915                 }
7916             }
7917         }
7918
7919         if (appData.icsActive) {
7920             if (appData.quietPlay &&
7921                 (gameMode == IcsPlayingWhite ||
7922                  gameMode == IcsPlayingBlack)) {
7923                 SendToICS(ics_prefix);
7924                 SendToICS("set shout 1\n");
7925             }
7926             nextGameMode = IcsIdle;
7927             ics_user_moved = FALSE;
7928             /* clean up premove.  It's ugly when the game has ended and the
7929              * premove highlights are still on the board.
7930              */
7931             if (gotPremove) {
7932               gotPremove = FALSE;
7933               ClearPremoveHighlights();
7934               DrawPosition(FALSE, boards[currentMove]);
7935             }
7936             if (whosays == GE_ICS) {
7937                 switch (result) {
7938                 case WhiteWins:
7939                     if (gameMode == IcsPlayingWhite)
7940                         PlayIcsWinSound();
7941                     else if(gameMode == IcsPlayingBlack)
7942                         PlayIcsLossSound();
7943                     break;
7944                 case BlackWins:
7945                     if (gameMode == IcsPlayingBlack)
7946                         PlayIcsWinSound();
7947                     else if(gameMode == IcsPlayingWhite)
7948                         PlayIcsLossSound();
7949                     break;
7950                 case GameIsDrawn:
7951                     PlayIcsDrawSound();
7952                     break;
7953                 default:
7954                     PlayIcsUnfinishedSound();
7955                 }
7956             }
7957         } else if (gameMode == EditGame ||
7958                    gameMode == PlayFromGameFile || 
7959                    gameMode == AnalyzeMode || 
7960                    gameMode == AnalyzeFile) {
7961             nextGameMode = gameMode;
7962         } else {
7963             nextGameMode = EndOfGame;
7964         }
7965         pausing = FALSE;
7966         ModeHighlight();
7967     } else {
7968         nextGameMode = gameMode;
7969     }
7970
7971     if (appData.noChessProgram) {
7972         gameMode = nextGameMode;
7973         ModeHighlight();
7974         endingGame = 0; /* [HGM] crash */
7975         return;
7976     }
7977
7978     if (first.reuse) {
7979         /* Put first chess program into idle state */
7980         if (first.pr != NoProc &&
7981             (gameMode == MachinePlaysWhite ||
7982              gameMode == MachinePlaysBlack ||
7983              gameMode == TwoMachinesPlay ||
7984              gameMode == IcsPlayingWhite ||
7985              gameMode == IcsPlayingBlack ||
7986              gameMode == BeginningOfGame)) {
7987             SendToProgram("force\n", &first);
7988             if (first.usePing) {
7989               char buf[MSG_SIZ];
7990               sprintf(buf, "ping %d\n", ++first.lastPing);
7991               SendToProgram(buf, &first);
7992             }
7993         }
7994     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7995         /* Kill off first chess program */
7996         if (first.isr != NULL)
7997           RemoveInputSource(first.isr);
7998         first.isr = NULL;
7999     
8000         if (first.pr != NoProc) {
8001             ExitAnalyzeMode();
8002             DoSleep( appData.delayBeforeQuit );
8003             SendToProgram("quit\n", &first);
8004             DoSleep( appData.delayAfterQuit );
8005             DestroyChildProcess(first.pr, first.useSigterm);
8006         }
8007         first.pr = NoProc;
8008     }
8009     if (second.reuse) {
8010         /* Put second chess program into idle state */
8011         if (second.pr != NoProc &&
8012             gameMode == TwoMachinesPlay) {
8013             SendToProgram("force\n", &second);
8014             if (second.usePing) {
8015               char buf[MSG_SIZ];
8016               sprintf(buf, "ping %d\n", ++second.lastPing);
8017               SendToProgram(buf, &second);
8018             }
8019         }
8020     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8021         /* Kill off second chess program */
8022         if (second.isr != NULL)
8023           RemoveInputSource(second.isr);
8024         second.isr = NULL;
8025     
8026         if (second.pr != NoProc) {
8027             DoSleep( appData.delayBeforeQuit );
8028             SendToProgram("quit\n", &second);
8029             DoSleep( appData.delayAfterQuit );
8030             DestroyChildProcess(second.pr, second.useSigterm);
8031         }
8032         second.pr = NoProc;
8033     }
8034
8035     if (matchMode && gameMode == TwoMachinesPlay) {
8036         switch (result) {
8037         case WhiteWins:
8038           if (first.twoMachinesColor[0] == 'w') {
8039             first.matchWins++;
8040           } else {
8041             second.matchWins++;
8042           }
8043           break;
8044         case BlackWins:
8045           if (first.twoMachinesColor[0] == 'b') {
8046             first.matchWins++;
8047           } else {
8048             second.matchWins++;
8049           }
8050           break;
8051         default:
8052           break;
8053         }
8054         if (matchGame < appData.matchGames) {
8055             char *tmp;
8056             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8057                 tmp = first.twoMachinesColor;
8058                 first.twoMachinesColor = second.twoMachinesColor;
8059                 second.twoMachinesColor = tmp;
8060             }
8061             gameMode = nextGameMode;
8062             matchGame++;
8063             if(appData.matchPause>10000 || appData.matchPause<10)
8064                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8065             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8066             endingGame = 0; /* [HGM] crash */
8067             return;
8068         } else {
8069             char buf[MSG_SIZ];
8070             gameMode = nextGameMode;
8071             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8072                     first.tidy, second.tidy,
8073                     first.matchWins, second.matchWins,
8074                     appData.matchGames - (first.matchWins + second.matchWins));
8075             DisplayFatalError(buf, 0, 0);
8076         }
8077     }
8078     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8079         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8080       ExitAnalyzeMode();
8081     gameMode = nextGameMode;
8082     ModeHighlight();
8083     endingGame = 0;  /* [HGM] crash */
8084 }
8085
8086 /* Assumes program was just initialized (initString sent).
8087    Leaves program in force mode. */
8088 void
8089 FeedMovesToProgram(cps, upto) 
8090      ChessProgramState *cps;
8091      int upto;
8092 {
8093     int i;
8094     
8095     if (appData.debugMode)
8096       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8097               startedFromSetupPosition ? "position and " : "",
8098               backwardMostMove, upto, cps->which);
8099     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8100         // [HGM] variantswitch: make engine aware of new variant
8101         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8102                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8103         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8104         SendToProgram(buf, cps);
8105         currentlyInitializedVariant = gameInfo.variant;
8106     }
8107     SendToProgram("force\n", cps);
8108     if (startedFromSetupPosition) {
8109         SendBoard(cps, backwardMostMove);
8110     if (appData.debugMode) {
8111         fprintf(debugFP, "feedMoves\n");
8112     }
8113     }
8114     for (i = backwardMostMove; i < upto; i++) {
8115         SendMoveToProgram(i, cps);
8116     }
8117 }
8118
8119
8120 void
8121 ResurrectChessProgram()
8122 {
8123      /* The chess program may have exited.
8124         If so, restart it and feed it all the moves made so far. */
8125
8126     if (appData.noChessProgram || first.pr != NoProc) return;
8127     
8128     StartChessProgram(&first);
8129     InitChessProgram(&first, FALSE);
8130     FeedMovesToProgram(&first, currentMove);
8131
8132     if (!first.sendTime) {
8133         /* can't tell gnuchess what its clock should read,
8134            so we bow to its notion. */
8135         ResetClocks();
8136         timeRemaining[0][currentMove] = whiteTimeRemaining;
8137         timeRemaining[1][currentMove] = blackTimeRemaining;
8138     }
8139
8140     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8141                 appData.icsEngineAnalyze) && first.analysisSupport) {
8142       SendToProgram("analyze\n", &first);
8143       first.analyzing = TRUE;
8144     }
8145 }
8146
8147 /*
8148  * Button procedures
8149  */
8150 void
8151 Reset(redraw, init)
8152      int redraw, init;
8153 {
8154     int i;
8155
8156     if (appData.debugMode) {
8157         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8158                 redraw, init, gameMode);
8159     }
8160     pausing = pauseExamInvalid = FALSE;
8161     startedFromSetupPosition = blackPlaysFirst = FALSE;
8162     firstMove = TRUE;
8163     whiteFlag = blackFlag = FALSE;
8164     userOfferedDraw = FALSE;
8165     hintRequested = bookRequested = FALSE;
8166     first.maybeThinking = FALSE;
8167     second.maybeThinking = FALSE;
8168     first.bookSuspend = FALSE; // [HGM] book
8169     second.bookSuspend = FALSE;
8170     thinkOutput[0] = NULLCHAR;
8171     lastHint[0] = NULLCHAR;
8172     ClearGameInfo(&gameInfo);
8173     gameInfo.variant = StringToVariant(appData.variant);
8174     ics_user_moved = ics_clock_paused = FALSE;
8175     ics_getting_history = H_FALSE;
8176     ics_gamenum = -1;
8177     white_holding[0] = black_holding[0] = NULLCHAR;
8178     ClearProgramStats();
8179     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8180     
8181     ResetFrontEnd();
8182     ClearHighlights();
8183     flipView = appData.flipView;
8184     ClearPremoveHighlights();
8185     gotPremove = FALSE;
8186     alarmSounded = FALSE;
8187
8188     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8189     if(appData.serverMovesName != NULL) {
8190         /* [HGM] prepare to make moves file for broadcasting */
8191         clock_t t = clock();
8192         if(serverMoves != NULL) fclose(serverMoves);
8193         serverMoves = fopen(appData.serverMovesName, "r");
8194         if(serverMoves != NULL) {
8195             fclose(serverMoves);
8196             /* delay 15 sec before overwriting, so all clients can see end */
8197             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8198         }
8199         serverMoves = fopen(appData.serverMovesName, "w");
8200     }
8201
8202     ExitAnalyzeMode();
8203     gameMode = BeginningOfGame;
8204     ModeHighlight();
8205     if(appData.icsActive) gameInfo.variant = VariantNormal;
8206     currentMove = forwardMostMove = backwardMostMove = 0;
8207     InitPosition(redraw);
8208     for (i = 0; i < MAX_MOVES; i++) {
8209         if (commentList[i] != NULL) {
8210             free(commentList[i]);
8211             commentList[i] = NULL;
8212         }
8213     }
8214     ResetClocks();
8215     timeRemaining[0][0] = whiteTimeRemaining;
8216     timeRemaining[1][0] = blackTimeRemaining;
8217     if (first.pr == NULL) {
8218         StartChessProgram(&first);
8219     }
8220     if (init) {
8221             InitChessProgram(&first, startedFromSetupPosition);
8222     }
8223     DisplayTitle("");
8224     DisplayMessage("", "");
8225     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8226     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8227 }
8228
8229 void
8230 AutoPlayGameLoop()
8231 {
8232     for (;;) {
8233         if (!AutoPlayOneMove())
8234           return;
8235         if (matchMode || appData.timeDelay == 0)
8236           continue;
8237         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8238           return;
8239         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8240         break;
8241     }
8242 }
8243
8244
8245 int
8246 AutoPlayOneMove()
8247 {
8248     int fromX, fromY, toX, toY;
8249
8250     if (appData.debugMode) {
8251       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8252     }
8253
8254     if (gameMode != PlayFromGameFile)
8255       return FALSE;
8256
8257     if (currentMove >= forwardMostMove) {
8258       gameMode = EditGame;
8259       ModeHighlight();
8260
8261       /* [AS] Clear current move marker at the end of a game */
8262       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8263
8264       return FALSE;
8265     }
8266     
8267     toX = moveList[currentMove][2] - AAA;
8268     toY = moveList[currentMove][3] - ONE;
8269
8270     if (moveList[currentMove][1] == '@') {
8271         if (appData.highlightLastMove) {
8272             SetHighlights(-1, -1, toX, toY);
8273         }
8274     } else {
8275         fromX = moveList[currentMove][0] - AAA;
8276         fromY = moveList[currentMove][1] - ONE;
8277
8278         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8279
8280         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8281
8282         if (appData.highlightLastMove) {
8283             SetHighlights(fromX, fromY, toX, toY);
8284         }
8285     }
8286     DisplayMove(currentMove);
8287     SendMoveToProgram(currentMove++, &first);
8288     DisplayBothClocks();
8289     DrawPosition(FALSE, boards[currentMove]);
8290     // [HGM] PV info: always display, routine tests if empty
8291     DisplayComment(currentMove - 1, commentList[currentMove]);
8292     return TRUE;
8293 }
8294
8295
8296 int
8297 LoadGameOneMove(readAhead)
8298      ChessMove readAhead;
8299 {
8300     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8301     char promoChar = NULLCHAR;
8302     ChessMove moveType;
8303     char move[MSG_SIZ];
8304     char *p, *q;
8305     
8306     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8307         gameMode != AnalyzeMode && gameMode != Training) {
8308         gameFileFP = NULL;
8309         return FALSE;
8310     }
8311     
8312     yyboardindex = forwardMostMove;
8313     if (readAhead != (ChessMove)0) {
8314       moveType = readAhead;
8315     } else {
8316       if (gameFileFP == NULL)
8317           return FALSE;
8318       moveType = (ChessMove) yylex();
8319     }
8320     
8321     done = FALSE;
8322     switch (moveType) {
8323       case Comment:
8324         if (appData.debugMode) 
8325           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8326         p = yy_text;
8327         if (*p == '{' || *p == '[' || *p == '(') {
8328             p[strlen(p) - 1] = NULLCHAR;
8329             p++;
8330         }
8331
8332         /* append the comment but don't display it */
8333         while (*p == '\n') p++;
8334         AppendComment(currentMove, p);
8335         return TRUE;
8336
8337       case WhiteCapturesEnPassant:
8338       case BlackCapturesEnPassant:
8339       case WhitePromotionChancellor:
8340       case BlackPromotionChancellor:
8341       case WhitePromotionArchbishop:
8342       case BlackPromotionArchbishop:
8343       case WhitePromotionCentaur:
8344       case BlackPromotionCentaur:
8345       case WhitePromotionQueen:
8346       case BlackPromotionQueen:
8347       case WhitePromotionRook:
8348       case BlackPromotionRook:
8349       case WhitePromotionBishop:
8350       case BlackPromotionBishop:
8351       case WhitePromotionKnight:
8352       case BlackPromotionKnight:
8353       case WhitePromotionKing:
8354       case BlackPromotionKing:
8355       case NormalMove:
8356       case WhiteKingSideCastle:
8357       case WhiteQueenSideCastle:
8358       case BlackKingSideCastle:
8359       case BlackQueenSideCastle:
8360       case WhiteKingSideCastleWild:
8361       case WhiteQueenSideCastleWild:
8362       case BlackKingSideCastleWild:
8363       case BlackQueenSideCastleWild:
8364       /* PUSH Fabien */
8365       case WhiteHSideCastleFR:
8366       case WhiteASideCastleFR:
8367       case BlackHSideCastleFR:
8368       case BlackASideCastleFR:
8369       /* POP Fabien */
8370         if (appData.debugMode)
8371           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8372         fromX = currentMoveString[0] - AAA;
8373         fromY = currentMoveString[1] - ONE;
8374         toX = currentMoveString[2] - AAA;
8375         toY = currentMoveString[3] - ONE;
8376         promoChar = currentMoveString[4];
8377         break;
8378
8379       case WhiteDrop:
8380       case BlackDrop:
8381         if (appData.debugMode)
8382           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8383         fromX = moveType == WhiteDrop ?
8384           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8385         (int) CharToPiece(ToLower(currentMoveString[0]));
8386         fromY = DROP_RANK;
8387         toX = currentMoveString[2] - AAA;
8388         toY = currentMoveString[3] - ONE;
8389         break;
8390
8391       case WhiteWins:
8392       case BlackWins:
8393       case GameIsDrawn:
8394       case GameUnfinished:
8395         if (appData.debugMode)
8396           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8397         p = strchr(yy_text, '{');
8398         if (p == NULL) p = strchr(yy_text, '(');
8399         if (p == NULL) {
8400             p = yy_text;
8401             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8402         } else {
8403             q = strchr(p, *p == '{' ? '}' : ')');
8404             if (q != NULL) *q = NULLCHAR;
8405             p++;
8406         }
8407         GameEnds(moveType, p, GE_FILE);
8408         done = TRUE;
8409         if (cmailMsgLoaded) {
8410             ClearHighlights();
8411             flipView = WhiteOnMove(currentMove);
8412             if (moveType == GameUnfinished) flipView = !flipView;
8413             if (appData.debugMode)
8414               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8415         }
8416         break;
8417
8418       case (ChessMove) 0:       /* end of file */
8419         if (appData.debugMode)
8420           fprintf(debugFP, "Parser hit end of file\n");
8421         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8422                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8423           case MT_NONE:
8424           case MT_CHECK:
8425             break;
8426           case MT_CHECKMATE:
8427           case MT_STAINMATE:
8428             if (WhiteOnMove(currentMove)) {
8429                 GameEnds(BlackWins, "Black mates", GE_FILE);
8430             } else {
8431                 GameEnds(WhiteWins, "White mates", GE_FILE);
8432             }
8433             break;
8434           case MT_STALEMATE:
8435             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8436             break;
8437         }
8438         done = TRUE;
8439         break;
8440
8441       case MoveNumberOne:
8442         if (lastLoadGameStart == GNUChessGame) {
8443             /* GNUChessGames have numbers, but they aren't move numbers */
8444             if (appData.debugMode)
8445               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8446                       yy_text, (int) moveType);
8447             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8448         }
8449         /* else fall thru */
8450
8451       case XBoardGame:
8452       case GNUChessGame:
8453       case PGNTag:
8454         /* Reached start of next game in file */
8455         if (appData.debugMode)
8456           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8457         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8458                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8459           case MT_NONE:
8460           case MT_CHECK:
8461             break;
8462           case MT_CHECKMATE:
8463           case MT_STAINMATE:
8464             if (WhiteOnMove(currentMove)) {
8465                 GameEnds(BlackWins, "Black mates", GE_FILE);
8466             } else {
8467                 GameEnds(WhiteWins, "White mates", GE_FILE);
8468             }
8469             break;
8470           case MT_STALEMATE:
8471             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8472             break;
8473         }
8474         done = TRUE;
8475         break;
8476
8477       case PositionDiagram:     /* should not happen; ignore */
8478       case ElapsedTime:         /* ignore */
8479       case NAG:                 /* ignore */
8480         if (appData.debugMode)
8481           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8482                   yy_text, (int) moveType);
8483         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8484
8485       case IllegalMove:
8486         if (appData.testLegality) {
8487             if (appData.debugMode)
8488               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8489             sprintf(move, _("Illegal move: %d.%s%s"),
8490                     (forwardMostMove / 2) + 1,
8491                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8492             DisplayError(move, 0);
8493             done = TRUE;
8494         } else {
8495             if (appData.debugMode)
8496               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8497                       yy_text, currentMoveString);
8498             fromX = currentMoveString[0] - AAA;
8499             fromY = currentMoveString[1] - ONE;
8500             toX = currentMoveString[2] - AAA;
8501             toY = currentMoveString[3] - ONE;
8502             promoChar = currentMoveString[4];
8503         }
8504         break;
8505
8506       case AmbiguousMove:
8507         if (appData.debugMode)
8508           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8509         sprintf(move, _("Ambiguous move: %d.%s%s"),
8510                 (forwardMostMove / 2) + 1,
8511                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8512         DisplayError(move, 0);
8513         done = TRUE;
8514         break;
8515
8516       default:
8517       case ImpossibleMove:
8518         if (appData.debugMode)
8519           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8520         sprintf(move, _("Illegal move: %d.%s%s"),
8521                 (forwardMostMove / 2) + 1,
8522                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8523         DisplayError(move, 0);
8524         done = TRUE;
8525         break;
8526     }
8527
8528     if (done) {
8529         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8530             DrawPosition(FALSE, boards[currentMove]);
8531             DisplayBothClocks();
8532             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8533               DisplayComment(currentMove - 1, commentList[currentMove]);
8534         }
8535         (void) StopLoadGameTimer();
8536         gameFileFP = NULL;
8537         cmailOldMove = forwardMostMove;
8538         return FALSE;
8539     } else {
8540         /* currentMoveString is set as a side-effect of yylex */
8541         strcat(currentMoveString, "\n");
8542         strcpy(moveList[forwardMostMove], currentMoveString);
8543         
8544         thinkOutput[0] = NULLCHAR;
8545         MakeMove(fromX, fromY, toX, toY, promoChar);
8546         currentMove = forwardMostMove;
8547         return TRUE;
8548     }
8549 }
8550
8551 /* Load the nth game from the given file */
8552 int
8553 LoadGameFromFile(filename, n, title, useList)
8554      char *filename;
8555      int n;
8556      char *title;
8557      /*Boolean*/ int useList;
8558 {
8559     FILE *f;
8560     char buf[MSG_SIZ];
8561
8562     if (strcmp(filename, "-") == 0) {
8563         f = stdin;
8564         title = "stdin";
8565     } else {
8566         f = fopen(filename, "rb");
8567         if (f == NULL) {
8568           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8569             DisplayError(buf, errno);
8570             return FALSE;
8571         }
8572     }
8573     if (fseek(f, 0, 0) == -1) {
8574         /* f is not seekable; probably a pipe */
8575         useList = FALSE;
8576     }
8577     if (useList && n == 0) {
8578         int error = GameListBuild(f);
8579         if (error) {
8580             DisplayError(_("Cannot build game list"), error);
8581         } else if (!ListEmpty(&gameList) &&
8582                    ((ListGame *) gameList.tailPred)->number > 1) {
8583             GameListPopUp(f, title);
8584             return TRUE;
8585         }
8586         GameListDestroy();
8587         n = 1;
8588     }
8589     if (n == 0) n = 1;
8590     return LoadGame(f, n, title, FALSE);
8591 }
8592
8593
8594 void
8595 MakeRegisteredMove()
8596 {
8597     int fromX, fromY, toX, toY;
8598     char promoChar;
8599     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8600         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8601           case CMAIL_MOVE:
8602           case CMAIL_DRAW:
8603             if (appData.debugMode)
8604               fprintf(debugFP, "Restoring %s for game %d\n",
8605                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8606     
8607             thinkOutput[0] = NULLCHAR;
8608             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8609             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8610             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8611             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8612             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8613             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8614             MakeMove(fromX, fromY, toX, toY, promoChar);
8615             ShowMove(fromX, fromY, toX, toY);
8616               
8617             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8618                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8619               case MT_NONE:
8620               case MT_CHECK:
8621                 break;
8622                 
8623               case MT_CHECKMATE:
8624               case MT_STAINMATE:
8625                 if (WhiteOnMove(currentMove)) {
8626                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8627                 } else {
8628                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8629                 }
8630                 break;
8631                 
8632               case MT_STALEMATE:
8633                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8634                 break;
8635             }
8636
8637             break;
8638             
8639           case CMAIL_RESIGN:
8640             if (WhiteOnMove(currentMove)) {
8641                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8642             } else {
8643                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8644             }
8645             break;
8646             
8647           case CMAIL_ACCEPT:
8648             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8649             break;
8650               
8651           default:
8652             break;
8653         }
8654     }
8655
8656     return;
8657 }
8658
8659 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8660 int
8661 CmailLoadGame(f, gameNumber, title, useList)
8662      FILE *f;
8663      int gameNumber;
8664      char *title;
8665      int useList;
8666 {
8667     int retVal;
8668
8669     if (gameNumber > nCmailGames) {
8670         DisplayError(_("No more games in this message"), 0);
8671         return FALSE;
8672     }
8673     if (f == lastLoadGameFP) {
8674         int offset = gameNumber - lastLoadGameNumber;
8675         if (offset == 0) {
8676             cmailMsg[0] = NULLCHAR;
8677             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8678                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8679                 nCmailMovesRegistered--;
8680             }
8681             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8682             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8683                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8684             }
8685         } else {
8686             if (! RegisterMove()) return FALSE;
8687         }
8688     }
8689
8690     retVal = LoadGame(f, gameNumber, title, useList);
8691
8692     /* Make move registered during previous look at this game, if any */
8693     MakeRegisteredMove();
8694
8695     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8696         commentList[currentMove]
8697           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8698         DisplayComment(currentMove - 1, commentList[currentMove]);
8699     }
8700
8701     return retVal;
8702 }
8703
8704 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8705 int
8706 ReloadGame(offset)
8707      int offset;
8708 {
8709     int gameNumber = lastLoadGameNumber + offset;
8710     if (lastLoadGameFP == NULL) {
8711         DisplayError(_("No game has been loaded yet"), 0);
8712         return FALSE;
8713     }
8714     if (gameNumber <= 0) {
8715         DisplayError(_("Can't back up any further"), 0);
8716         return FALSE;
8717     }
8718     if (cmailMsgLoaded) {
8719         return CmailLoadGame(lastLoadGameFP, gameNumber,
8720                              lastLoadGameTitle, lastLoadGameUseList);
8721     } else {
8722         return LoadGame(lastLoadGameFP, gameNumber,
8723                         lastLoadGameTitle, lastLoadGameUseList);
8724     }
8725 }
8726
8727
8728
8729 /* Load the nth game from open file f */
8730 int
8731 LoadGame(f, gameNumber, title, useList)
8732      FILE *f;
8733      int gameNumber;
8734      char *title;
8735      int useList;
8736 {
8737     ChessMove cm;
8738     char buf[MSG_SIZ];
8739     int gn = gameNumber;
8740     ListGame *lg = NULL;
8741     int numPGNTags = 0;
8742     int err;
8743     GameMode oldGameMode;
8744     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8745
8746     if (appData.debugMode) 
8747         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8748
8749     if (gameMode == Training )
8750         SetTrainingModeOff();
8751
8752     oldGameMode = gameMode;
8753     if (gameMode != BeginningOfGame) {
8754       Reset(FALSE, TRUE);
8755     }
8756
8757     gameFileFP = f;
8758     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8759         fclose(lastLoadGameFP);
8760     }
8761
8762     if (useList) {
8763         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8764         
8765         if (lg) {
8766             fseek(f, lg->offset, 0);
8767             GameListHighlight(gameNumber);
8768             gn = 1;
8769         }
8770         else {
8771             DisplayError(_("Game number out of range"), 0);
8772             return FALSE;
8773         }
8774     } else {
8775         GameListDestroy();
8776         if (fseek(f, 0, 0) == -1) {
8777             if (f == lastLoadGameFP ?
8778                 gameNumber == lastLoadGameNumber + 1 :
8779                 gameNumber == 1) {
8780                 gn = 1;
8781             } else {
8782                 DisplayError(_("Can't seek on game file"), 0);
8783                 return FALSE;
8784             }
8785         }
8786     }
8787     lastLoadGameFP = f;
8788     lastLoadGameNumber = gameNumber;
8789     strcpy(lastLoadGameTitle, title);
8790     lastLoadGameUseList = useList;
8791
8792     yynewfile(f);
8793
8794     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8795       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8796                 lg->gameInfo.black);
8797             DisplayTitle(buf);
8798     } else if (*title != NULLCHAR) {
8799         if (gameNumber > 1) {
8800             sprintf(buf, "%s %d", title, gameNumber);
8801             DisplayTitle(buf);
8802         } else {
8803             DisplayTitle(title);
8804         }
8805     }
8806
8807     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8808         gameMode = PlayFromGameFile;
8809         ModeHighlight();
8810     }
8811
8812     currentMove = forwardMostMove = backwardMostMove = 0;
8813     CopyBoard(boards[0], initialPosition);
8814     StopClocks();
8815
8816     /*
8817      * Skip the first gn-1 games in the file.
8818      * Also skip over anything that precedes an identifiable 
8819      * start of game marker, to avoid being confused by 
8820      * garbage at the start of the file.  Currently 
8821      * recognized start of game markers are the move number "1",
8822      * the pattern "gnuchess .* game", the pattern
8823      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8824      * A game that starts with one of the latter two patterns
8825      * will also have a move number 1, possibly
8826      * following a position diagram.
8827      * 5-4-02: Let's try being more lenient and allowing a game to
8828      * start with an unnumbered move.  Does that break anything?
8829      */
8830     cm = lastLoadGameStart = (ChessMove) 0;
8831     while (gn > 0) {
8832         yyboardindex = forwardMostMove;
8833         cm = (ChessMove) yylex();
8834         switch (cm) {
8835           case (ChessMove) 0:
8836             if (cmailMsgLoaded) {
8837                 nCmailGames = CMAIL_MAX_GAMES - gn;
8838             } else {
8839                 Reset(TRUE, TRUE);
8840                 DisplayError(_("Game not found in file"), 0);
8841             }
8842             return FALSE;
8843
8844           case GNUChessGame:
8845           case XBoardGame:
8846             gn--;
8847             lastLoadGameStart = cm;
8848             break;
8849             
8850           case MoveNumberOne:
8851             switch (lastLoadGameStart) {
8852               case GNUChessGame:
8853               case XBoardGame:
8854               case PGNTag:
8855                 break;
8856               case MoveNumberOne:
8857               case (ChessMove) 0:
8858                 gn--;           /* count this game */
8859                 lastLoadGameStart = cm;
8860                 break;
8861               default:
8862                 /* impossible */
8863                 break;
8864             }
8865             break;
8866
8867           case PGNTag:
8868             switch (lastLoadGameStart) {
8869               case GNUChessGame:
8870               case PGNTag:
8871               case MoveNumberOne:
8872               case (ChessMove) 0:
8873                 gn--;           /* count this game */
8874                 lastLoadGameStart = cm;
8875                 break;
8876               case XBoardGame:
8877                 lastLoadGameStart = cm; /* game counted already */
8878                 break;
8879               default:
8880                 /* impossible */
8881                 break;
8882             }
8883             if (gn > 0) {
8884                 do {
8885                     yyboardindex = forwardMostMove;
8886                     cm = (ChessMove) yylex();
8887                 } while (cm == PGNTag || cm == Comment);
8888             }
8889             break;
8890
8891           case WhiteWins:
8892           case BlackWins:
8893           case GameIsDrawn:
8894             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8895                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8896                     != CMAIL_OLD_RESULT) {
8897                     nCmailResults ++ ;
8898                     cmailResult[  CMAIL_MAX_GAMES
8899                                 - gn - 1] = CMAIL_OLD_RESULT;
8900                 }
8901             }
8902             break;
8903
8904           case NormalMove:
8905             /* Only a NormalMove can be at the start of a game
8906              * without a position diagram. */
8907             if (lastLoadGameStart == (ChessMove) 0) {
8908               gn--;
8909               lastLoadGameStart = MoveNumberOne;
8910             }
8911             break;
8912
8913           default:
8914             break;
8915         }
8916     }
8917     
8918     if (appData.debugMode)
8919       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8920
8921     if (cm == XBoardGame) {
8922         /* Skip any header junk before position diagram and/or move 1 */
8923         for (;;) {
8924             yyboardindex = forwardMostMove;
8925             cm = (ChessMove) yylex();
8926
8927             if (cm == (ChessMove) 0 ||
8928                 cm == GNUChessGame || cm == XBoardGame) {
8929                 /* Empty game; pretend end-of-file and handle later */
8930                 cm = (ChessMove) 0;
8931                 break;
8932             }
8933
8934             if (cm == MoveNumberOne || cm == PositionDiagram ||
8935                 cm == PGNTag || cm == Comment)
8936               break;
8937         }
8938     } else if (cm == GNUChessGame) {
8939         if (gameInfo.event != NULL) {
8940             free(gameInfo.event);
8941         }
8942         gameInfo.event = StrSave(yy_text);
8943     }   
8944
8945     startedFromSetupPosition = FALSE;
8946     while (cm == PGNTag) {
8947         if (appData.debugMode) 
8948           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8949         err = ParsePGNTag(yy_text, &gameInfo);
8950         if (!err) numPGNTags++;
8951
8952         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8953         if(gameInfo.variant != oldVariant) {
8954             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8955             InitPosition(TRUE);
8956             oldVariant = gameInfo.variant;
8957             if (appData.debugMode) 
8958               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8959         }
8960
8961
8962         if (gameInfo.fen != NULL) {
8963           Board initial_position;
8964           startedFromSetupPosition = TRUE;
8965           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8966             Reset(TRUE, TRUE);
8967             DisplayError(_("Bad FEN position in file"), 0);
8968             return FALSE;
8969           }
8970           CopyBoard(boards[0], initial_position);
8971           if (blackPlaysFirst) {
8972             currentMove = forwardMostMove = backwardMostMove = 1;
8973             CopyBoard(boards[1], initial_position);
8974             strcpy(moveList[0], "");
8975             strcpy(parseList[0], "");
8976             timeRemaining[0][1] = whiteTimeRemaining;
8977             timeRemaining[1][1] = blackTimeRemaining;
8978             if (commentList[0] != NULL) {
8979               commentList[1] = commentList[0];
8980               commentList[0] = NULL;
8981             }
8982           } else {
8983             currentMove = forwardMostMove = backwardMostMove = 0;
8984           }
8985           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8986           {   int i;
8987               initialRulePlies = FENrulePlies;
8988               epStatus[forwardMostMove] = FENepStatus;
8989               for( i=0; i< nrCastlingRights; i++ )
8990                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8991           }
8992           yyboardindex = forwardMostMove;
8993           free(gameInfo.fen);
8994           gameInfo.fen = NULL;
8995         }
8996
8997         yyboardindex = forwardMostMove;
8998         cm = (ChessMove) yylex();
8999
9000         /* Handle comments interspersed among the tags */
9001         while (cm == Comment) {
9002             char *p;
9003             if (appData.debugMode) 
9004               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9005             p = yy_text;
9006             if (*p == '{' || *p == '[' || *p == '(') {
9007                 p[strlen(p) - 1] = NULLCHAR;
9008                 p++;
9009             }
9010             while (*p == '\n') p++;
9011             AppendComment(currentMove, p);
9012             yyboardindex = forwardMostMove;
9013             cm = (ChessMove) yylex();
9014         }
9015     }
9016
9017     /* don't rely on existence of Event tag since if game was
9018      * pasted from clipboard the Event tag may not exist
9019      */
9020     if (numPGNTags > 0){
9021         char *tags;
9022         if (gameInfo.variant == VariantNormal) {
9023           gameInfo.variant = StringToVariant(gameInfo.event);
9024         }
9025         if (!matchMode) {
9026           if( appData.autoDisplayTags ) {
9027             tags = PGNTags(&gameInfo);
9028             TagsPopUp(tags, CmailMsg());
9029             free(tags);
9030           }
9031         }
9032     } else {
9033         /* Make something up, but don't display it now */
9034         SetGameInfo();
9035         TagsPopDown();
9036     }
9037
9038     if (cm == PositionDiagram) {
9039         int i, j;
9040         char *p;
9041         Board initial_position;
9042
9043         if (appData.debugMode)
9044           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9045
9046         if (!startedFromSetupPosition) {
9047             p = yy_text;
9048             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9049               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9050                 switch (*p) {
9051                   case '[':
9052                   case '-':
9053                   case ' ':
9054                   case '\t':
9055                   case '\n':
9056                   case '\r':
9057                     break;
9058                   default:
9059                     initial_position[i][j++] = CharToPiece(*p);
9060                     break;
9061                 }
9062             while (*p == ' ' || *p == '\t' ||
9063                    *p == '\n' || *p == '\r') p++;
9064         
9065             if (strncmp(p, "black", strlen("black"))==0)
9066               blackPlaysFirst = TRUE;
9067             else
9068               blackPlaysFirst = FALSE;
9069             startedFromSetupPosition = TRUE;
9070         
9071             CopyBoard(boards[0], initial_position);
9072             if (blackPlaysFirst) {
9073                 currentMove = forwardMostMove = backwardMostMove = 1;
9074                 CopyBoard(boards[1], initial_position);
9075                 strcpy(moveList[0], "");
9076                 strcpy(parseList[0], "");
9077                 timeRemaining[0][1] = whiteTimeRemaining;
9078                 timeRemaining[1][1] = blackTimeRemaining;
9079                 if (commentList[0] != NULL) {
9080                     commentList[1] = commentList[0];
9081                     commentList[0] = NULL;
9082                 }
9083             } else {
9084                 currentMove = forwardMostMove = backwardMostMove = 0;
9085             }
9086         }
9087         yyboardindex = forwardMostMove;
9088         cm = (ChessMove) yylex();
9089     }
9090
9091     if (first.pr == NoProc) {
9092         StartChessProgram(&first);
9093     }
9094     InitChessProgram(&first, FALSE);
9095     SendToProgram("force\n", &first);
9096     if (startedFromSetupPosition) {
9097         SendBoard(&first, forwardMostMove);
9098     if (appData.debugMode) {
9099         fprintf(debugFP, "Load Game\n");
9100     }
9101         DisplayBothClocks();
9102     }      
9103
9104     /* [HGM] server: flag to write setup moves in broadcast file as one */
9105     loadFlag = appData.suppressLoadMoves;
9106
9107     while (cm == Comment) {
9108         char *p;
9109         if (appData.debugMode) 
9110           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9111         p = yy_text;
9112         if (*p == '{' || *p == '[' || *p == '(') {
9113             p[strlen(p) - 1] = NULLCHAR;
9114             p++;
9115         }
9116         while (*p == '\n') p++;
9117         AppendComment(currentMove, p);
9118         yyboardindex = forwardMostMove;
9119         cm = (ChessMove) yylex();
9120     }
9121
9122     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9123         cm == WhiteWins || cm == BlackWins ||
9124         cm == GameIsDrawn || cm == GameUnfinished) {
9125         DisplayMessage("", _("No moves in game"));
9126         if (cmailMsgLoaded) {
9127             if (appData.debugMode)
9128               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9129             ClearHighlights();
9130             flipView = FALSE;
9131         }
9132         DrawPosition(FALSE, boards[currentMove]);
9133         DisplayBothClocks();
9134         gameMode = EditGame;
9135         ModeHighlight();
9136         gameFileFP = NULL;
9137         cmailOldMove = 0;
9138         return TRUE;
9139     }
9140
9141     // [HGM] PV info: routine tests if comment empty
9142     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9143         DisplayComment(currentMove - 1, commentList[currentMove]);
9144     }
9145     if (!matchMode && appData.timeDelay != 0) 
9146       DrawPosition(FALSE, boards[currentMove]);
9147
9148     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9149       programStats.ok_to_send = 1;
9150     }
9151
9152     /* if the first token after the PGN tags is a move
9153      * and not move number 1, retrieve it from the parser 
9154      */
9155     if (cm != MoveNumberOne)
9156         LoadGameOneMove(cm);
9157
9158     /* load the remaining moves from the file */
9159     while (LoadGameOneMove((ChessMove)0)) {
9160       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9161       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9162     }
9163
9164     /* rewind to the start of the game */
9165     currentMove = backwardMostMove;
9166
9167     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9168
9169     if (oldGameMode == AnalyzeFile ||
9170         oldGameMode == AnalyzeMode) {
9171       AnalyzeFileEvent();
9172     }
9173
9174     if (matchMode || appData.timeDelay == 0) {
9175       ToEndEvent();
9176       gameMode = EditGame;
9177       ModeHighlight();
9178     } else if (appData.timeDelay > 0) {
9179       AutoPlayGameLoop();
9180     }
9181
9182     if (appData.debugMode) 
9183         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9184
9185     loadFlag = 0; /* [HGM] true game starts */
9186     return TRUE;
9187 }
9188
9189 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9190 int
9191 ReloadPosition(offset)
9192      int offset;
9193 {
9194     int positionNumber = lastLoadPositionNumber + offset;
9195     if (lastLoadPositionFP == NULL) {
9196         DisplayError(_("No position has been loaded yet"), 0);
9197         return FALSE;
9198     }
9199     if (positionNumber <= 0) {
9200         DisplayError(_("Can't back up any further"), 0);
9201         return FALSE;
9202     }
9203     return LoadPosition(lastLoadPositionFP, positionNumber,
9204                         lastLoadPositionTitle);
9205 }
9206
9207 /* Load the nth position from the given file */
9208 int
9209 LoadPositionFromFile(filename, n, title)
9210      char *filename;
9211      int n;
9212      char *title;
9213 {
9214     FILE *f;
9215     char buf[MSG_SIZ];
9216
9217     if (strcmp(filename, "-") == 0) {
9218         return LoadPosition(stdin, n, "stdin");
9219     } else {
9220         f = fopen(filename, "rb");
9221         if (f == NULL) {
9222             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9223             DisplayError(buf, errno);
9224             return FALSE;
9225         } else {
9226             return LoadPosition(f, n, title);
9227         }
9228     }
9229 }
9230
9231 /* Load the nth position from the given open file, and close it */
9232 int
9233 LoadPosition(f, positionNumber, title)
9234      FILE *f;
9235      int positionNumber;
9236      char *title;
9237 {
9238     char *p, line[MSG_SIZ];
9239     Board initial_position;
9240     int i, j, fenMode, pn;
9241     
9242     if (gameMode == Training )
9243         SetTrainingModeOff();
9244
9245     if (gameMode != BeginningOfGame) {
9246         Reset(FALSE, TRUE);
9247     }
9248     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9249         fclose(lastLoadPositionFP);
9250     }
9251     if (positionNumber == 0) positionNumber = 1;
9252     lastLoadPositionFP = f;
9253     lastLoadPositionNumber = positionNumber;
9254     strcpy(lastLoadPositionTitle, title);
9255     if (first.pr == NoProc) {
9256       StartChessProgram(&first);
9257       InitChessProgram(&first, FALSE);
9258     }    
9259     pn = positionNumber;
9260     if (positionNumber < 0) {
9261         /* Negative position number means to seek to that byte offset */
9262         if (fseek(f, -positionNumber, 0) == -1) {
9263             DisplayError(_("Can't seek on position file"), 0);
9264             return FALSE;
9265         };
9266         pn = 1;
9267     } else {
9268         if (fseek(f, 0, 0) == -1) {
9269             if (f == lastLoadPositionFP ?
9270                 positionNumber == lastLoadPositionNumber + 1 :
9271                 positionNumber == 1) {
9272                 pn = 1;
9273             } else {
9274                 DisplayError(_("Can't seek on position file"), 0);
9275                 return FALSE;
9276             }
9277         }
9278     }
9279     /* See if this file is FEN or old-style xboard */
9280     if (fgets(line, MSG_SIZ, f) == NULL) {
9281         DisplayError(_("Position not found in file"), 0);
9282         return FALSE;
9283     }
9284     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9285     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9286
9287     if (pn >= 2) {
9288         if (fenMode || line[0] == '#') pn--;
9289         while (pn > 0) {
9290             /* skip positions before number pn */
9291             if (fgets(line, MSG_SIZ, f) == NULL) {
9292                 Reset(TRUE, TRUE);
9293                 DisplayError(_("Position not found in file"), 0);
9294                 return FALSE;
9295             }
9296             if (fenMode || line[0] == '#') pn--;
9297         }
9298     }
9299
9300     if (fenMode) {
9301         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9302             DisplayError(_("Bad FEN position in file"), 0);
9303             return FALSE;
9304         }
9305     } else {
9306         (void) fgets(line, MSG_SIZ, f);
9307         (void) fgets(line, MSG_SIZ, f);
9308     
9309         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9310             (void) fgets(line, MSG_SIZ, f);
9311             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9312                 if (*p == ' ')
9313                   continue;
9314                 initial_position[i][j++] = CharToPiece(*p);
9315             }
9316         }
9317     
9318         blackPlaysFirst = FALSE;
9319         if (!feof(f)) {
9320             (void) fgets(line, MSG_SIZ, f);
9321             if (strncmp(line, "black", strlen("black"))==0)
9322               blackPlaysFirst = TRUE;
9323         }
9324     }
9325     startedFromSetupPosition = TRUE;
9326     
9327     SendToProgram("force\n", &first);
9328     CopyBoard(boards[0], initial_position);
9329     if (blackPlaysFirst) {
9330         currentMove = forwardMostMove = backwardMostMove = 1;
9331         strcpy(moveList[0], "");
9332         strcpy(parseList[0], "");
9333         CopyBoard(boards[1], initial_position);
9334         DisplayMessage("", _("Black to play"));
9335     } else {
9336         currentMove = forwardMostMove = backwardMostMove = 0;
9337         DisplayMessage("", _("White to play"));
9338     }
9339           /* [HGM] copy FEN attributes as well */
9340           {   int i;
9341               initialRulePlies = FENrulePlies;
9342               epStatus[forwardMostMove] = FENepStatus;
9343               for( i=0; i< nrCastlingRights; i++ )
9344                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9345           }
9346     SendBoard(&first, forwardMostMove);
9347     if (appData.debugMode) {
9348 int i, j;
9349   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9350   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9351         fprintf(debugFP, "Load Position\n");
9352     }
9353
9354     if (positionNumber > 1) {
9355         sprintf(line, "%s %d", title, positionNumber);
9356         DisplayTitle(line);
9357     } else {
9358         DisplayTitle(title);
9359     }
9360     gameMode = EditGame;
9361     ModeHighlight();
9362     ResetClocks();
9363     timeRemaining[0][1] = whiteTimeRemaining;
9364     timeRemaining[1][1] = blackTimeRemaining;
9365     DrawPosition(FALSE, boards[currentMove]);
9366    
9367     return TRUE;
9368 }
9369
9370
9371 void
9372 CopyPlayerNameIntoFileName(dest, src)
9373      char **dest, *src;
9374 {
9375     while (*src != NULLCHAR && *src != ',') {
9376         if (*src == ' ') {
9377             *(*dest)++ = '_';
9378             src++;
9379         } else {
9380             *(*dest)++ = *src++;
9381         }
9382     }
9383 }
9384
9385 char *DefaultFileName(ext)
9386      char *ext;
9387 {
9388     static char def[MSG_SIZ];
9389     char *p;
9390
9391     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9392         p = def;
9393         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9394         *p++ = '-';
9395         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9396         *p++ = '.';
9397         strcpy(p, ext);
9398     } else {
9399         def[0] = NULLCHAR;
9400     }
9401     return def;
9402 }
9403
9404 /* Save the current game to the given file */
9405 int
9406 SaveGameToFile(filename, append)
9407      char *filename;
9408      int append;
9409 {
9410     FILE *f;
9411     char buf[MSG_SIZ];
9412
9413     if (strcmp(filename, "-") == 0) {
9414         return SaveGame(stdout, 0, NULL);
9415     } else {
9416         f = fopen(filename, append ? "a" : "w");
9417         if (f == NULL) {
9418             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9419             DisplayError(buf, errno);
9420             return FALSE;
9421         } else {
9422             return SaveGame(f, 0, NULL);
9423         }
9424     }
9425 }
9426
9427 char *
9428 SavePart(str)
9429      char *str;
9430 {
9431     static char buf[MSG_SIZ];
9432     char *p;
9433     
9434     p = strchr(str, ' ');
9435     if (p == NULL) return str;
9436     strncpy(buf, str, p - str);
9437     buf[p - str] = NULLCHAR;
9438     return buf;
9439 }
9440
9441 #define PGN_MAX_LINE 75
9442
9443 #define PGN_SIDE_WHITE  0
9444 #define PGN_SIDE_BLACK  1
9445
9446 /* [AS] */
9447 static int FindFirstMoveOutOfBook( int side )
9448 {
9449     int result = -1;
9450
9451     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9452         int index = backwardMostMove;
9453         int has_book_hit = 0;
9454
9455         if( (index % 2) != side ) {
9456             index++;
9457         }
9458
9459         while( index < forwardMostMove ) {
9460             /* Check to see if engine is in book */
9461             int depth = pvInfoList[index].depth;
9462             int score = pvInfoList[index].score;
9463             int in_book = 0;
9464
9465             if( depth <= 2 ) {
9466                 in_book = 1;
9467             }
9468             else if( score == 0 && depth == 63 ) {
9469                 in_book = 1; /* Zappa */
9470             }
9471             else if( score == 2 && depth == 99 ) {
9472                 in_book = 1; /* Abrok */
9473             }
9474
9475             has_book_hit += in_book;
9476
9477             if( ! in_book ) {
9478                 result = index;
9479
9480                 break;
9481             }
9482
9483             index += 2;
9484         }
9485     }
9486
9487     return result;
9488 }
9489
9490 /* [AS] */
9491 void GetOutOfBookInfo( char * buf )
9492 {
9493     int oob[2];
9494     int i;
9495     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9496
9497     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9498     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9499
9500     *buf = '\0';
9501
9502     if( oob[0] >= 0 || oob[1] >= 0 ) {
9503         for( i=0; i<2; i++ ) {
9504             int idx = oob[i];
9505
9506             if( idx >= 0 ) {
9507                 if( i > 0 && oob[0] >= 0 ) {
9508                     strcat( buf, "   " );
9509                 }
9510
9511                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9512                 sprintf( buf+strlen(buf), "%s%.2f", 
9513                     pvInfoList[idx].score >= 0 ? "+" : "",
9514                     pvInfoList[idx].score / 100.0 );
9515             }
9516         }
9517     }
9518 }
9519
9520 /* Save game in PGN style and close the file */
9521 int
9522 SaveGamePGN(f)
9523      FILE *f;
9524 {
9525     int i, offset, linelen, newblock;
9526     time_t tm;
9527 //    char *movetext;
9528     char numtext[32];
9529     int movelen, numlen, blank;
9530     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9531
9532     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9533     
9534     tm = time((time_t *) NULL);
9535     
9536     PrintPGNTags(f, &gameInfo);
9537     
9538     if (backwardMostMove > 0 || startedFromSetupPosition) {
9539         char *fen = PositionToFEN(backwardMostMove, NULL);
9540         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9541         fprintf(f, "\n{--------------\n");
9542         PrintPosition(f, backwardMostMove);
9543         fprintf(f, "--------------}\n");
9544         free(fen);
9545     }
9546     else {
9547         /* [AS] Out of book annotation */
9548         if( appData.saveOutOfBookInfo ) {
9549             char buf[64];
9550
9551             GetOutOfBookInfo( buf );
9552
9553             if( buf[0] != '\0' ) {
9554                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9555             }
9556         }
9557
9558         fprintf(f, "\n");
9559     }
9560
9561     i = backwardMostMove;
9562     linelen = 0;
9563     newblock = TRUE;
9564
9565     while (i < forwardMostMove) {
9566         /* Print comments preceding this move */
9567         if (commentList[i] != NULL) {
9568             if (linelen > 0) fprintf(f, "\n");
9569             fprintf(f, "{\n%s}\n", commentList[i]);
9570             linelen = 0;
9571             newblock = TRUE;
9572         }
9573
9574         /* Format move number */
9575         if ((i % 2) == 0) {
9576             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9577         } else {
9578             if (newblock) {
9579                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9580             } else {
9581                 numtext[0] = NULLCHAR;
9582             }
9583         }
9584         numlen = strlen(numtext);
9585         newblock = FALSE;
9586
9587         /* Print move number */
9588         blank = linelen > 0 && numlen > 0;
9589         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9590             fprintf(f, "\n");
9591             linelen = 0;
9592             blank = 0;
9593         }
9594         if (blank) {
9595             fprintf(f, " ");
9596             linelen++;
9597         }
9598         fprintf(f, numtext);
9599         linelen += numlen;
9600
9601         /* Get move */
9602         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9603         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9604
9605         /* Print move */
9606         blank = linelen > 0 && movelen > 0;
9607         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9608             fprintf(f, "\n");
9609             linelen = 0;
9610             blank = 0;
9611         }
9612         if (blank) {
9613             fprintf(f, " ");
9614             linelen++;
9615         }
9616         fprintf(f, move_buffer);
9617         linelen += movelen;
9618
9619         /* [AS] Add PV info if present */
9620         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9621             /* [HGM] add time */
9622             char buf[MSG_SIZ]; int seconds = 0;
9623
9624             if(i >= backwardMostMove) {
9625                 if(WhiteOnMove(i))
9626                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9627                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9628                 else
9629                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9630                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9631             }
9632             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9633
9634             if( seconds <= 0) buf[0] = 0; else
9635             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9636                 seconds = (seconds + 4)/10; // round to full seconds
9637                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9638                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9639             }
9640
9641             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9642                 pvInfoList[i].score >= 0 ? "+" : "",
9643                 pvInfoList[i].score / 100.0,
9644                 pvInfoList[i].depth,
9645                 buf );
9646
9647             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9648
9649             /* Print score/depth */
9650             blank = linelen > 0 && movelen > 0;
9651             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9652                 fprintf(f, "\n");
9653                 linelen = 0;
9654                 blank = 0;
9655             }
9656             if (blank) {
9657                 fprintf(f, " ");
9658                 linelen++;
9659             }
9660             fprintf(f, move_buffer);
9661             linelen += movelen;
9662         }
9663
9664         i++;
9665     }
9666     
9667     /* Start a new line */
9668     if (linelen > 0) fprintf(f, "\n");
9669
9670     /* Print comments after last move */
9671     if (commentList[i] != NULL) {
9672         fprintf(f, "{\n%s}\n", commentList[i]);
9673     }
9674
9675     /* Print result */
9676     if (gameInfo.resultDetails != NULL &&
9677         gameInfo.resultDetails[0] != NULLCHAR) {
9678         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9679                 PGNResult(gameInfo.result));
9680     } else {
9681         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9682     }
9683
9684     fclose(f);
9685     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9686     return TRUE;
9687 }
9688
9689 /* Save game in old style and close the file */
9690 int
9691 SaveGameOldStyle(f)
9692      FILE *f;
9693 {
9694     int i, offset;
9695     time_t tm;
9696     
9697     tm = time((time_t *) NULL);
9698     
9699     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9700     PrintOpponents(f);
9701     
9702     if (backwardMostMove > 0 || startedFromSetupPosition) {
9703         fprintf(f, "\n[--------------\n");
9704         PrintPosition(f, backwardMostMove);
9705         fprintf(f, "--------------]\n");
9706     } else {
9707         fprintf(f, "\n");
9708     }
9709
9710     i = backwardMostMove;
9711     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9712
9713     while (i < forwardMostMove) {
9714         if (commentList[i] != NULL) {
9715             fprintf(f, "[%s]\n", commentList[i]);
9716         }
9717
9718         if ((i % 2) == 1) {
9719             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9720             i++;
9721         } else {
9722             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9723             i++;
9724             if (commentList[i] != NULL) {
9725                 fprintf(f, "\n");
9726                 continue;
9727             }
9728             if (i >= forwardMostMove) {
9729                 fprintf(f, "\n");
9730                 break;
9731             }
9732             fprintf(f, "%s\n", parseList[i]);
9733             i++;
9734         }
9735     }
9736     
9737     if (commentList[i] != NULL) {
9738         fprintf(f, "[%s]\n", commentList[i]);
9739     }
9740
9741     /* This isn't really the old style, but it's close enough */
9742     if (gameInfo.resultDetails != NULL &&
9743         gameInfo.resultDetails[0] != NULLCHAR) {
9744         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9745                 gameInfo.resultDetails);
9746     } else {
9747         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9748     }
9749
9750     fclose(f);
9751     return TRUE;
9752 }
9753
9754 /* Save the current game to open file f and close the file */
9755 int
9756 SaveGame(f, dummy, dummy2)
9757      FILE *f;
9758      int dummy;
9759      char *dummy2;
9760 {
9761     if (gameMode == EditPosition) EditPositionDone();
9762     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9763     if (appData.oldSaveStyle)
9764       return SaveGameOldStyle(f);
9765     else
9766       return SaveGamePGN(f);
9767 }
9768
9769 /* Save the current position to the given file */
9770 int
9771 SavePositionToFile(filename)
9772      char *filename;
9773 {
9774     FILE *f;
9775     char buf[MSG_SIZ];
9776
9777     if (strcmp(filename, "-") == 0) {
9778         return SavePosition(stdout, 0, NULL);
9779     } else {
9780         f = fopen(filename, "a");
9781         if (f == NULL) {
9782             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9783             DisplayError(buf, errno);
9784             return FALSE;
9785         } else {
9786             SavePosition(f, 0, NULL);
9787             return TRUE;
9788         }
9789     }
9790 }
9791
9792 /* Save the current position to the given open file and close the file */
9793 int
9794 SavePosition(f, dummy, dummy2)
9795      FILE *f;
9796      int dummy;
9797      char *dummy2;
9798 {
9799     time_t tm;
9800     char *fen;
9801     
9802     if (appData.oldSaveStyle) {
9803         tm = time((time_t *) NULL);
9804     
9805         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9806         PrintOpponents(f);
9807         fprintf(f, "[--------------\n");
9808         PrintPosition(f, currentMove);
9809         fprintf(f, "--------------]\n");
9810     } else {
9811         fen = PositionToFEN(currentMove, NULL);
9812         fprintf(f, "%s\n", fen);
9813         free(fen);
9814     }
9815     fclose(f);
9816     return TRUE;
9817 }
9818
9819 void
9820 ReloadCmailMsgEvent(unregister)
9821      int unregister;
9822 {
9823 #if !WIN32
9824     static char *inFilename = NULL;
9825     static char *outFilename;
9826     int i;
9827     struct stat inbuf, outbuf;
9828     int status;
9829     
9830     /* Any registered moves are unregistered if unregister is set, */
9831     /* i.e. invoked by the signal handler */
9832     if (unregister) {
9833         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9834             cmailMoveRegistered[i] = FALSE;
9835             if (cmailCommentList[i] != NULL) {
9836                 free(cmailCommentList[i]);
9837                 cmailCommentList[i] = NULL;
9838             }
9839         }
9840         nCmailMovesRegistered = 0;
9841     }
9842
9843     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9844         cmailResult[i] = CMAIL_NOT_RESULT;
9845     }
9846     nCmailResults = 0;
9847
9848     if (inFilename == NULL) {
9849         /* Because the filenames are static they only get malloced once  */
9850         /* and they never get freed                                      */
9851         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9852         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9853
9854         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9855         sprintf(outFilename, "%s.out", appData.cmailGameName);
9856     }
9857     
9858     status = stat(outFilename, &outbuf);
9859     if (status < 0) {
9860         cmailMailedMove = FALSE;
9861     } else {
9862         status = stat(inFilename, &inbuf);
9863         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9864     }
9865     
9866     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9867        counts the games, notes how each one terminated, etc.
9868        
9869        It would be nice to remove this kludge and instead gather all
9870        the information while building the game list.  (And to keep it
9871        in the game list nodes instead of having a bunch of fixed-size
9872        parallel arrays.)  Note this will require getting each game's
9873        termination from the PGN tags, as the game list builder does
9874        not process the game moves.  --mann
9875        */
9876     cmailMsgLoaded = TRUE;
9877     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9878     
9879     /* Load first game in the file or popup game menu */
9880     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9881
9882 #endif /* !WIN32 */
9883     return;
9884 }
9885
9886 int
9887 RegisterMove()
9888 {
9889     FILE *f;
9890     char string[MSG_SIZ];
9891
9892     if (   cmailMailedMove
9893         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9894         return TRUE;            /* Allow free viewing  */
9895     }
9896
9897     /* Unregister move to ensure that we don't leave RegisterMove        */
9898     /* with the move registered when the conditions for registering no   */
9899     /* longer hold                                                       */
9900     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9901         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9902         nCmailMovesRegistered --;
9903
9904         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9905           {
9906               free(cmailCommentList[lastLoadGameNumber - 1]);
9907               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9908           }
9909     }
9910
9911     if (cmailOldMove == -1) {
9912         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9913         return FALSE;
9914     }
9915
9916     if (currentMove > cmailOldMove + 1) {
9917         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9918         return FALSE;
9919     }
9920
9921     if (currentMove < cmailOldMove) {
9922         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9923         return FALSE;
9924     }
9925
9926     if (forwardMostMove > currentMove) {
9927         /* Silently truncate extra moves */
9928         TruncateGame();
9929     }
9930
9931     if (   (currentMove == cmailOldMove + 1)
9932         || (   (currentMove == cmailOldMove)
9933             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9934                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9935         if (gameInfo.result != GameUnfinished) {
9936             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9937         }
9938
9939         if (commentList[currentMove] != NULL) {
9940             cmailCommentList[lastLoadGameNumber - 1]
9941               = StrSave(commentList[currentMove]);
9942         }
9943         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9944
9945         if (appData.debugMode)
9946           fprintf(debugFP, "Saving %s for game %d\n",
9947                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9948
9949         sprintf(string,
9950                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9951         
9952         f = fopen(string, "w");
9953         if (appData.oldSaveStyle) {
9954             SaveGameOldStyle(f); /* also closes the file */
9955             
9956             sprintf(string, "%s.pos.out", appData.cmailGameName);
9957             f = fopen(string, "w");
9958             SavePosition(f, 0, NULL); /* also closes the file */
9959         } else {
9960             fprintf(f, "{--------------\n");
9961             PrintPosition(f, currentMove);
9962             fprintf(f, "--------------}\n\n");
9963             
9964             SaveGame(f, 0, NULL); /* also closes the file*/
9965         }
9966         
9967         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9968         nCmailMovesRegistered ++;
9969     } else if (nCmailGames == 1) {
9970         DisplayError(_("You have not made a move yet"), 0);
9971         return FALSE;
9972     }
9973
9974     return TRUE;
9975 }
9976
9977 void
9978 MailMoveEvent()
9979 {
9980 #if !WIN32
9981     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9982     FILE *commandOutput;
9983     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9984     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9985     int nBuffers;
9986     int i;
9987     int archived;
9988     char *arcDir;
9989
9990     if (! cmailMsgLoaded) {
9991         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9992         return;
9993     }
9994
9995     if (nCmailGames == nCmailResults) {
9996         DisplayError(_("No unfinished games"), 0);
9997         return;
9998     }
9999
10000 #if CMAIL_PROHIBIT_REMAIL
10001     if (cmailMailedMove) {
10002         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);
10003         DisplayError(msg, 0);
10004         return;
10005     }
10006 #endif
10007
10008     if (! (cmailMailedMove || RegisterMove())) return;
10009     
10010     if (   cmailMailedMove
10011         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10012         sprintf(string, partCommandString,
10013                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10014         commandOutput = popen(string, "r");
10015
10016         if (commandOutput == NULL) {
10017             DisplayError(_("Failed to invoke cmail"), 0);
10018         } else {
10019             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10020                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10021             }
10022             if (nBuffers > 1) {
10023                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10024                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10025                 nBytes = MSG_SIZ - 1;
10026             } else {
10027                 (void) memcpy(msg, buffer, nBytes);
10028             }
10029             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10030
10031             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10032                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10033
10034                 archived = TRUE;
10035                 for (i = 0; i < nCmailGames; i ++) {
10036                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10037                         archived = FALSE;
10038                     }
10039                 }
10040                 if (   archived
10041                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10042                         != NULL)) {
10043                     sprintf(buffer, "%s/%s.%s.archive",
10044                             arcDir,
10045                             appData.cmailGameName,
10046                             gameInfo.date);
10047                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10048                     cmailMsgLoaded = FALSE;
10049                 }
10050             }
10051
10052             DisplayInformation(msg);
10053             pclose(commandOutput);
10054         }
10055     } else {
10056         if ((*cmailMsg) != '\0') {
10057             DisplayInformation(cmailMsg);
10058         }
10059     }
10060
10061     return;
10062 #endif /* !WIN32 */
10063 }
10064
10065 char *
10066 CmailMsg()
10067 {
10068 #if WIN32
10069     return NULL;
10070 #else
10071     int  prependComma = 0;
10072     char number[5];
10073     char string[MSG_SIZ];       /* Space for game-list */
10074     int  i;
10075     
10076     if (!cmailMsgLoaded) return "";
10077
10078     if (cmailMailedMove) {
10079         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10080     } else {
10081         /* Create a list of games left */
10082         sprintf(string, "[");
10083         for (i = 0; i < nCmailGames; i ++) {
10084             if (! (   cmailMoveRegistered[i]
10085                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10086                 if (prependComma) {
10087                     sprintf(number, ",%d", i + 1);
10088                 } else {
10089                     sprintf(number, "%d", i + 1);
10090                     prependComma = 1;
10091                 }
10092                 
10093                 strcat(string, number);
10094             }
10095         }
10096         strcat(string, "]");
10097
10098         if (nCmailMovesRegistered + nCmailResults == 0) {
10099             switch (nCmailGames) {
10100               case 1:
10101                 sprintf(cmailMsg,
10102                         _("Still need to make move for game\n"));
10103                 break;
10104                 
10105               case 2:
10106                 sprintf(cmailMsg,
10107                         _("Still need to make moves for both games\n"));
10108                 break;
10109                 
10110               default:
10111                 sprintf(cmailMsg,
10112                         _("Still need to make moves for all %d games\n"),
10113                         nCmailGames);
10114                 break;
10115             }
10116         } else {
10117             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10118               case 1:
10119                 sprintf(cmailMsg,
10120                         _("Still need to make a move for game %s\n"),
10121                         string);
10122                 break;
10123                 
10124               case 0:
10125                 if (nCmailResults == nCmailGames) {
10126                     sprintf(cmailMsg, _("No unfinished games\n"));
10127                 } else {
10128                     sprintf(cmailMsg, _("Ready to send mail\n"));
10129                 }
10130                 break;
10131                 
10132               default:
10133                 sprintf(cmailMsg,
10134                         _("Still need to make moves for games %s\n"),
10135                         string);
10136             }
10137         }
10138     }
10139     return cmailMsg;
10140 #endif /* WIN32 */
10141 }
10142
10143 void
10144 ResetGameEvent()
10145 {
10146     if (gameMode == Training)
10147       SetTrainingModeOff();
10148
10149     Reset(TRUE, TRUE);
10150     cmailMsgLoaded = FALSE;
10151     if (appData.icsActive) {
10152       SendToICS(ics_prefix);
10153       SendToICS("refresh\n");
10154     }
10155 }
10156
10157 void
10158 ExitEvent(status)
10159      int status;
10160 {
10161     exiting++;
10162     if (exiting > 2) {
10163       /* Give up on clean exit */
10164       exit(status);
10165     }
10166     if (exiting > 1) {
10167       /* Keep trying for clean exit */
10168       return;
10169     }
10170
10171     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10172
10173     if (telnetISR != NULL) {
10174       RemoveInputSource(telnetISR);
10175     }
10176     if (icsPR != NoProc) {
10177       DestroyChildProcess(icsPR, TRUE);
10178     }
10179
10180     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10181     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10182
10183     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10184     /* make sure this other one finishes before killing it!                  */
10185     if(endingGame) { int count = 0;
10186         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10187         while(endingGame && count++ < 10) DoSleep(1);
10188         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10189     }
10190
10191     /* Kill off chess programs */
10192     if (first.pr != NoProc) {
10193         ExitAnalyzeMode();
10194         
10195         DoSleep( appData.delayBeforeQuit );
10196         SendToProgram("quit\n", &first);
10197         DoSleep( appData.delayAfterQuit );
10198         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10199     }
10200     if (second.pr != NoProc) {
10201         DoSleep( appData.delayBeforeQuit );
10202         SendToProgram("quit\n", &second);
10203         DoSleep( appData.delayAfterQuit );
10204         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10205     }
10206     if (first.isr != NULL) {
10207         RemoveInputSource(first.isr);
10208     }
10209     if (second.isr != NULL) {
10210         RemoveInputSource(second.isr);
10211     }
10212
10213     ShutDownFrontEnd();
10214     exit(status);
10215 }
10216
10217 void
10218 PauseEvent()
10219 {
10220     if (appData.debugMode)
10221         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10222     if (pausing) {
10223         pausing = FALSE;
10224         ModeHighlight();
10225         if (gameMode == MachinePlaysWhite ||
10226             gameMode == MachinePlaysBlack) {
10227             StartClocks();
10228         } else {
10229             DisplayBothClocks();
10230         }
10231         if (gameMode == PlayFromGameFile) {
10232             if (appData.timeDelay >= 0) 
10233                 AutoPlayGameLoop();
10234         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10235             Reset(FALSE, TRUE);
10236             SendToICS(ics_prefix);
10237             SendToICS("refresh\n");
10238         } else if (currentMove < forwardMostMove) {
10239             ForwardInner(forwardMostMove);
10240         }
10241         pauseExamInvalid = FALSE;
10242     } else {
10243         switch (gameMode) {
10244           default:
10245             return;
10246           case IcsExamining:
10247             pauseExamForwardMostMove = forwardMostMove;
10248             pauseExamInvalid = FALSE;
10249             /* fall through */
10250           case IcsObserving:
10251           case IcsPlayingWhite:
10252           case IcsPlayingBlack:
10253             pausing = TRUE;
10254             ModeHighlight();
10255             return;
10256           case PlayFromGameFile:
10257             (void) StopLoadGameTimer();
10258             pausing = TRUE;
10259             ModeHighlight();
10260             break;
10261           case BeginningOfGame:
10262             if (appData.icsActive) return;
10263             /* else fall through */
10264           case MachinePlaysWhite:
10265           case MachinePlaysBlack:
10266           case TwoMachinesPlay:
10267             if (forwardMostMove == 0)
10268               return;           /* don't pause if no one has moved */
10269             if ((gameMode == MachinePlaysWhite &&
10270                  !WhiteOnMove(forwardMostMove)) ||
10271                 (gameMode == MachinePlaysBlack &&
10272                  WhiteOnMove(forwardMostMove))) {
10273                 StopClocks();
10274             }
10275             pausing = TRUE;
10276             ModeHighlight();
10277             break;
10278         }
10279     }
10280 }
10281
10282 void
10283 EditCommentEvent()
10284 {
10285     char title[MSG_SIZ];
10286
10287     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10288         strcpy(title, _("Edit comment"));
10289     } else {
10290         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10291                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10292                 parseList[currentMove - 1]);
10293     }
10294
10295     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10296 }
10297
10298
10299 void
10300 EditTagsEvent()
10301 {
10302     char *tags = PGNTags(&gameInfo);
10303     EditTagsPopUp(tags);
10304     free(tags);
10305 }
10306
10307 void
10308 AnalyzeModeEvent()
10309 {
10310     if (appData.noChessProgram || gameMode == AnalyzeMode)
10311       return;
10312
10313     if (gameMode != AnalyzeFile) {
10314         if (!appData.icsEngineAnalyze) {
10315                EditGameEvent();
10316                if (gameMode != EditGame) return;
10317         }
10318         ResurrectChessProgram();
10319         SendToProgram("analyze\n", &first);
10320         first.analyzing = TRUE;
10321         /*first.maybeThinking = TRUE;*/
10322         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10323         AnalysisPopUp(_("Analysis"),
10324                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10325     }
10326     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10327     pausing = FALSE;
10328     ModeHighlight();
10329     SetGameInfo();
10330
10331     StartAnalysisClock();
10332     GetTimeMark(&lastNodeCountTime);
10333     lastNodeCount = 0;
10334 }
10335
10336 void
10337 AnalyzeFileEvent()
10338 {
10339     if (appData.noChessProgram || gameMode == AnalyzeFile)
10340       return;
10341
10342     if (gameMode != AnalyzeMode) {
10343         EditGameEvent();
10344         if (gameMode != EditGame) return;
10345         ResurrectChessProgram();
10346         SendToProgram("analyze\n", &first);
10347         first.analyzing = TRUE;
10348         /*first.maybeThinking = TRUE;*/
10349         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10350         AnalysisPopUp(_("Analysis"),
10351                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10352     }
10353     gameMode = AnalyzeFile;
10354     pausing = FALSE;
10355     ModeHighlight();
10356     SetGameInfo();
10357
10358     StartAnalysisClock();
10359     GetTimeMark(&lastNodeCountTime);
10360     lastNodeCount = 0;
10361 }
10362
10363 void
10364 MachineWhiteEvent()
10365 {
10366     char buf[MSG_SIZ];
10367     char *bookHit = NULL;
10368
10369     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10370       return;
10371
10372
10373     if (gameMode == PlayFromGameFile || 
10374         gameMode == TwoMachinesPlay  || 
10375         gameMode == Training         || 
10376         gameMode == AnalyzeMode      || 
10377         gameMode == EndOfGame)
10378         EditGameEvent();
10379
10380     if (gameMode == EditPosition) 
10381         EditPositionDone();
10382
10383     if (!WhiteOnMove(currentMove)) {
10384         DisplayError(_("It is not White's turn"), 0);
10385         return;
10386     }
10387   
10388     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10389       ExitAnalyzeMode();
10390
10391     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10392         gameMode == AnalyzeFile)
10393         TruncateGame();
10394
10395     ResurrectChessProgram();    /* in case it isn't running */
10396     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10397         gameMode = MachinePlaysWhite;
10398         ResetClocks();
10399     } else
10400     gameMode = MachinePlaysWhite;
10401     pausing = FALSE;
10402     ModeHighlight();
10403     SetGameInfo();
10404     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10405     DisplayTitle(buf);
10406     if (first.sendName) {
10407       sprintf(buf, "name %s\n", gameInfo.black);
10408       SendToProgram(buf, &first);
10409     }
10410     if (first.sendTime) {
10411       if (first.useColors) {
10412         SendToProgram("black\n", &first); /*gnu kludge*/
10413       }
10414       SendTimeRemaining(&first, TRUE);
10415     }
10416     if (first.useColors) {
10417       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10418     }
10419     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10420     SetMachineThinkingEnables();
10421     first.maybeThinking = TRUE;
10422     StartClocks();
10423     firstMove = FALSE;
10424
10425     if (appData.autoFlipView && !flipView) {
10426       flipView = !flipView;
10427       DrawPosition(FALSE, NULL);
10428       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10429     }
10430
10431     if(bookHit) { // [HGM] book: simulate book reply
10432         static char bookMove[MSG_SIZ]; // a bit generous?
10433
10434         programStats.nodes = programStats.depth = programStats.time = 
10435         programStats.score = programStats.got_only_move = 0;
10436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10437
10438         strcpy(bookMove, "move ");
10439         strcat(bookMove, bookHit);
10440         HandleMachineMove(bookMove, &first);
10441     }
10442 }
10443
10444 void
10445 MachineBlackEvent()
10446 {
10447     char buf[MSG_SIZ];
10448    char *bookHit = NULL;
10449
10450     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10451         return;
10452
10453
10454     if (gameMode == PlayFromGameFile || 
10455         gameMode == TwoMachinesPlay  || 
10456         gameMode == Training         || 
10457         gameMode == AnalyzeMode      || 
10458         gameMode == EndOfGame)
10459         EditGameEvent();
10460
10461     if (gameMode == EditPosition) 
10462         EditPositionDone();
10463
10464     if (WhiteOnMove(currentMove)) {
10465         DisplayError(_("It is not Black's turn"), 0);
10466         return;
10467     }
10468     
10469     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10470       ExitAnalyzeMode();
10471
10472     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10473         gameMode == AnalyzeFile)
10474         TruncateGame();
10475
10476     ResurrectChessProgram();    /* in case it isn't running */
10477     gameMode = MachinePlaysBlack;
10478     pausing = FALSE;
10479     ModeHighlight();
10480     SetGameInfo();
10481     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10482     DisplayTitle(buf);
10483     if (first.sendName) {
10484       sprintf(buf, "name %s\n", gameInfo.white);
10485       SendToProgram(buf, &first);
10486     }
10487     if (first.sendTime) {
10488       if (first.useColors) {
10489         SendToProgram("white\n", &first); /*gnu kludge*/
10490       }
10491       SendTimeRemaining(&first, FALSE);
10492     }
10493     if (first.useColors) {
10494       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10495     }
10496     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10497     SetMachineThinkingEnables();
10498     first.maybeThinking = TRUE;
10499     StartClocks();
10500
10501     if (appData.autoFlipView && flipView) {
10502       flipView = !flipView;
10503       DrawPosition(FALSE, NULL);
10504       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10505     }
10506     if(bookHit) { // [HGM] book: simulate book reply
10507         static char bookMove[MSG_SIZ]; // a bit generous?
10508
10509         programStats.nodes = programStats.depth = programStats.time = 
10510         programStats.score = programStats.got_only_move = 0;
10511         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10512
10513         strcpy(bookMove, "move ");
10514         strcat(bookMove, bookHit);
10515         HandleMachineMove(bookMove, &first);
10516     }
10517 }
10518
10519
10520 void
10521 DisplayTwoMachinesTitle()
10522 {
10523     char buf[MSG_SIZ];
10524     if (appData.matchGames > 0) {
10525         if (first.twoMachinesColor[0] == 'w') {
10526             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10527                     gameInfo.white, gameInfo.black,
10528                     first.matchWins, second.matchWins,
10529                     matchGame - 1 - (first.matchWins + second.matchWins));
10530         } else {
10531             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10532                     gameInfo.white, gameInfo.black,
10533                     second.matchWins, first.matchWins,
10534                     matchGame - 1 - (first.matchWins + second.matchWins));
10535         }
10536     } else {
10537         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10538     }
10539     DisplayTitle(buf);
10540 }
10541
10542 void
10543 TwoMachinesEvent P((void))
10544 {
10545     int i;
10546     char buf[MSG_SIZ];
10547     ChessProgramState *onmove;
10548     char *bookHit = NULL;
10549     
10550     if (appData.noChessProgram) return;
10551
10552     switch (gameMode) {
10553       case TwoMachinesPlay:
10554         return;
10555       case MachinePlaysWhite:
10556       case MachinePlaysBlack:
10557         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10558             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10559             return;
10560         }
10561         /* fall through */
10562       case BeginningOfGame:
10563       case PlayFromGameFile:
10564       case EndOfGame:
10565         EditGameEvent();
10566         if (gameMode != EditGame) return;
10567         break;
10568       case EditPosition:
10569         EditPositionDone();
10570         break;
10571       case AnalyzeMode:
10572       case AnalyzeFile:
10573         ExitAnalyzeMode();
10574         break;
10575       case EditGame:
10576       default:
10577         break;
10578     }
10579
10580     forwardMostMove = currentMove;
10581     ResurrectChessProgram();    /* in case first program isn't running */
10582
10583     if (second.pr == NULL) {
10584         StartChessProgram(&second);
10585         if (second.protocolVersion == 1) {
10586           TwoMachinesEventIfReady();
10587         } else {
10588           /* kludge: allow timeout for initial "feature" command */
10589           FreezeUI();
10590           DisplayMessage("", _("Starting second chess program"));
10591           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10592         }
10593         return;
10594     }
10595     DisplayMessage("", "");
10596     InitChessProgram(&second, FALSE);
10597     SendToProgram("force\n", &second);
10598     if (startedFromSetupPosition) {
10599         SendBoard(&second, backwardMostMove);
10600     if (appData.debugMode) {
10601         fprintf(debugFP, "Two Machines\n");
10602     }
10603     }
10604     for (i = backwardMostMove; i < forwardMostMove; i++) {
10605         SendMoveToProgram(i, &second);
10606     }
10607
10608     gameMode = TwoMachinesPlay;
10609     pausing = FALSE;
10610     ModeHighlight();
10611     SetGameInfo();
10612     DisplayTwoMachinesTitle();
10613     firstMove = TRUE;
10614     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10615         onmove = &first;
10616     } else {
10617         onmove = &second;
10618     }
10619
10620     SendToProgram(first.computerString, &first);
10621     if (first.sendName) {
10622       sprintf(buf, "name %s\n", second.tidy);
10623       SendToProgram(buf, &first);
10624     }
10625     SendToProgram(second.computerString, &second);
10626     if (second.sendName) {
10627       sprintf(buf, "name %s\n", first.tidy);
10628       SendToProgram(buf, &second);
10629     }
10630
10631     ResetClocks();
10632     if (!first.sendTime || !second.sendTime) {
10633         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10634         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10635     }
10636     if (onmove->sendTime) {
10637       if (onmove->useColors) {
10638         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10639       }
10640       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10641     }
10642     if (onmove->useColors) {
10643       SendToProgram(onmove->twoMachinesColor, onmove);
10644     }
10645     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10646 //    SendToProgram("go\n", onmove);
10647     onmove->maybeThinking = TRUE;
10648     SetMachineThinkingEnables();
10649
10650     StartClocks();
10651
10652     if(bookHit) { // [HGM] book: simulate book reply
10653         static char bookMove[MSG_SIZ]; // a bit generous?
10654
10655         programStats.nodes = programStats.depth = programStats.time = 
10656         programStats.score = programStats.got_only_move = 0;
10657         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10658
10659         strcpy(bookMove, "move ");
10660         strcat(bookMove, bookHit);
10661         HandleMachineMove(bookMove, &first);
10662     }
10663 }
10664
10665 void
10666 TrainingEvent()
10667 {
10668     if (gameMode == Training) {
10669       SetTrainingModeOff();
10670       gameMode = PlayFromGameFile;
10671       DisplayMessage("", _("Training mode off"));
10672     } else {
10673       gameMode = Training;
10674       animateTraining = appData.animate;
10675
10676       /* make sure we are not already at the end of the game */
10677       if (currentMove < forwardMostMove) {
10678         SetTrainingModeOn();
10679         DisplayMessage("", _("Training mode on"));
10680       } else {
10681         gameMode = PlayFromGameFile;
10682         DisplayError(_("Already at end of game"), 0);
10683       }
10684     }
10685     ModeHighlight();
10686 }
10687
10688 void
10689 IcsClientEvent()
10690 {
10691     if (!appData.icsActive) return;
10692     switch (gameMode) {
10693       case IcsPlayingWhite:
10694       case IcsPlayingBlack:
10695       case IcsObserving:
10696       case IcsIdle:
10697       case BeginningOfGame:
10698       case IcsExamining:
10699         return;
10700
10701       case EditGame:
10702         break;
10703
10704       case EditPosition:
10705         EditPositionDone();
10706         break;
10707
10708       case AnalyzeMode:
10709       case AnalyzeFile:
10710         ExitAnalyzeMode();
10711         break;
10712         
10713       default:
10714         EditGameEvent();
10715         break;
10716     }
10717
10718     gameMode = IcsIdle;
10719     ModeHighlight();
10720     return;
10721 }
10722
10723
10724 void
10725 EditGameEvent()
10726 {
10727     int i;
10728
10729     switch (gameMode) {
10730       case Training:
10731         SetTrainingModeOff();
10732         break;
10733       case MachinePlaysWhite:
10734       case MachinePlaysBlack:
10735       case BeginningOfGame:
10736         SendToProgram("force\n", &first);
10737         SetUserThinkingEnables();
10738         break;
10739       case PlayFromGameFile:
10740         (void) StopLoadGameTimer();
10741         if (gameFileFP != NULL) {
10742             gameFileFP = NULL;
10743         }
10744         break;
10745       case EditPosition:
10746         EditPositionDone();
10747         break;
10748       case AnalyzeMode:
10749       case AnalyzeFile:
10750         ExitAnalyzeMode();
10751         SendToProgram("force\n", &first);
10752         break;
10753       case TwoMachinesPlay:
10754         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10755         ResurrectChessProgram();
10756         SetUserThinkingEnables();
10757         break;
10758       case EndOfGame:
10759         ResurrectChessProgram();
10760         break;
10761       case IcsPlayingBlack:
10762       case IcsPlayingWhite:
10763         DisplayError(_("Warning: You are still playing a game"), 0);
10764         break;
10765       case IcsObserving:
10766         DisplayError(_("Warning: You are still observing a game"), 0);
10767         break;
10768       case IcsExamining:
10769         DisplayError(_("Warning: You are still examining a game"), 0);
10770         break;
10771       case IcsIdle:
10772         break;
10773       case EditGame:
10774       default:
10775         return;
10776     }
10777     
10778     pausing = FALSE;
10779     StopClocks();
10780     first.offeredDraw = second.offeredDraw = 0;
10781
10782     if (gameMode == PlayFromGameFile) {
10783         whiteTimeRemaining = timeRemaining[0][currentMove];
10784         blackTimeRemaining = timeRemaining[1][currentMove];
10785         DisplayTitle("");
10786     }
10787
10788     if (gameMode == MachinePlaysWhite ||
10789         gameMode == MachinePlaysBlack ||
10790         gameMode == TwoMachinesPlay ||
10791         gameMode == EndOfGame) {
10792         i = forwardMostMove;
10793         while (i > currentMove) {
10794             SendToProgram("undo\n", &first);
10795             i--;
10796         }
10797         whiteTimeRemaining = timeRemaining[0][currentMove];
10798         blackTimeRemaining = timeRemaining[1][currentMove];
10799         DisplayBothClocks();
10800         if (whiteFlag || blackFlag) {
10801             whiteFlag = blackFlag = 0;
10802         }
10803         DisplayTitle("");
10804     }           
10805     
10806     gameMode = EditGame;
10807     ModeHighlight();
10808     SetGameInfo();
10809 }
10810
10811
10812 void
10813 EditPositionEvent()
10814 {
10815     if (gameMode == EditPosition) {
10816         EditGameEvent();
10817         return;
10818     }
10819     
10820     EditGameEvent();
10821     if (gameMode != EditGame) return;
10822     
10823     gameMode = EditPosition;
10824     ModeHighlight();
10825     SetGameInfo();
10826     if (currentMove > 0)
10827       CopyBoard(boards[0], boards[currentMove]);
10828     
10829     blackPlaysFirst = !WhiteOnMove(currentMove);
10830     ResetClocks();
10831     currentMove = forwardMostMove = backwardMostMove = 0;
10832     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10833     DisplayMove(-1);
10834 }
10835
10836 void
10837 ExitAnalyzeMode()
10838 {
10839     /* [DM] icsEngineAnalyze - possible call from other functions */
10840     if (appData.icsEngineAnalyze) {
10841         appData.icsEngineAnalyze = FALSE;
10842
10843         DisplayMessage("",_("Close ICS engine analyze..."));
10844     }
10845     if (first.analysisSupport && first.analyzing) {
10846       SendToProgram("exit\n", &first);
10847       first.analyzing = FALSE;
10848     }
10849     AnalysisPopDown();
10850     thinkOutput[0] = NULLCHAR;
10851 }
10852
10853 void
10854 EditPositionDone()
10855 {
10856     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10857
10858     startedFromSetupPosition = TRUE;
10859     InitChessProgram(&first, FALSE);
10860     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10861     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10862         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10863         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10864     } else castlingRights[0][2] = -1;
10865     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10866         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10867         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10868     } else castlingRights[0][5] = -1;
10869     SendToProgram("force\n", &first);
10870     if (blackPlaysFirst) {
10871         strcpy(moveList[0], "");
10872         strcpy(parseList[0], "");
10873         currentMove = forwardMostMove = backwardMostMove = 1;
10874         CopyBoard(boards[1], boards[0]);
10875         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10876         { int i;
10877           epStatus[1] = epStatus[0];
10878           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10879         }
10880     } else {
10881         currentMove = forwardMostMove = backwardMostMove = 0;
10882     }
10883     SendBoard(&first, forwardMostMove);
10884     if (appData.debugMode) {
10885         fprintf(debugFP, "EditPosDone\n");
10886     }
10887     DisplayTitle("");
10888     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10889     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10890     gameMode = EditGame;
10891     ModeHighlight();
10892     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10893     ClearHighlights(); /* [AS] */
10894 }
10895
10896 /* Pause for `ms' milliseconds */
10897 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10898 void
10899 TimeDelay(ms)
10900      long ms;
10901 {
10902     TimeMark m1, m2;
10903
10904     GetTimeMark(&m1);
10905     do {
10906         GetTimeMark(&m2);
10907     } while (SubtractTimeMarks(&m2, &m1) < ms);
10908 }
10909
10910 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10911 void
10912 SendMultiLineToICS(buf)
10913      char *buf;
10914 {
10915     char temp[MSG_SIZ+1], *p;
10916     int len;
10917
10918     len = strlen(buf);
10919     if (len > MSG_SIZ)
10920       len = MSG_SIZ;
10921   
10922     strncpy(temp, buf, len);
10923     temp[len] = 0;
10924
10925     p = temp;
10926     while (*p) {
10927         if (*p == '\n' || *p == '\r')
10928           *p = ' ';
10929         ++p;
10930     }
10931
10932     strcat(temp, "\n");
10933     SendToICS(temp);
10934     SendToPlayer(temp, strlen(temp));
10935 }
10936
10937 void
10938 SetWhiteToPlayEvent()
10939 {
10940     if (gameMode == EditPosition) {
10941         blackPlaysFirst = FALSE;
10942         DisplayBothClocks();    /* works because currentMove is 0 */
10943     } else if (gameMode == IcsExamining) {
10944         SendToICS(ics_prefix);
10945         SendToICS("tomove white\n");
10946     }
10947 }
10948
10949 void
10950 SetBlackToPlayEvent()
10951 {
10952     if (gameMode == EditPosition) {
10953         blackPlaysFirst = TRUE;
10954         currentMove = 1;        /* kludge */
10955         DisplayBothClocks();
10956         currentMove = 0;
10957     } else if (gameMode == IcsExamining) {
10958         SendToICS(ics_prefix);
10959         SendToICS("tomove black\n");
10960     }
10961 }
10962
10963 void
10964 EditPositionMenuEvent(selection, x, y)
10965      ChessSquare selection;
10966      int x, y;
10967 {
10968     char buf[MSG_SIZ];
10969     ChessSquare piece = boards[0][y][x];
10970
10971     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10972
10973     switch (selection) {
10974       case ClearBoard:
10975         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10976             SendToICS(ics_prefix);
10977             SendToICS("bsetup clear\n");
10978         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10979             SendToICS(ics_prefix);
10980             SendToICS("clearboard\n");
10981         } else {
10982             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10983                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10984                 for (y = 0; y < BOARD_HEIGHT; y++) {
10985                     if (gameMode == IcsExamining) {
10986                         if (boards[currentMove][y][x] != EmptySquare) {
10987                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10988                                     AAA + x, ONE + y);
10989                             SendToICS(buf);
10990                         }
10991                     } else {
10992                         boards[0][y][x] = p;
10993                     }
10994                 }
10995             }
10996         }
10997         if (gameMode == EditPosition) {
10998             DrawPosition(FALSE, boards[0]);
10999         }
11000         break;
11001
11002       case WhitePlay:
11003         SetWhiteToPlayEvent();
11004         break;
11005
11006       case BlackPlay:
11007         SetBlackToPlayEvent();
11008         break;
11009
11010       case EmptySquare:
11011         if (gameMode == IcsExamining) {
11012             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11013             SendToICS(buf);
11014         } else {
11015             boards[0][y][x] = EmptySquare;
11016             DrawPosition(FALSE, boards[0]);
11017         }
11018         break;
11019
11020       case PromotePiece:
11021         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11022            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11023             selection = (ChessSquare) (PROMOTED piece);
11024         } else if(piece == EmptySquare) selection = WhiteSilver;
11025         else selection = (ChessSquare)((int)piece - 1);
11026         goto defaultlabel;
11027
11028       case DemotePiece:
11029         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11030            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11031             selection = (ChessSquare) (DEMOTED piece);
11032         } else if(piece == EmptySquare) selection = BlackSilver;
11033         else selection = (ChessSquare)((int)piece + 1);       
11034         goto defaultlabel;
11035
11036       case WhiteQueen:
11037       case BlackQueen:
11038         if(gameInfo.variant == VariantShatranj ||
11039            gameInfo.variant == VariantXiangqi  ||
11040            gameInfo.variant == VariantCourier    )
11041             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11042         goto defaultlabel;
11043
11044       case WhiteKing:
11045       case BlackKing:
11046         if(gameInfo.variant == VariantXiangqi)
11047             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11048         if(gameInfo.variant == VariantKnightmate)
11049             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11050       default:
11051         defaultlabel:
11052         if (gameMode == IcsExamining) {
11053             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11054                     PieceToChar(selection), AAA + x, ONE + y);
11055             SendToICS(buf);
11056         } else {
11057             boards[0][y][x] = selection;
11058             DrawPosition(FALSE, boards[0]);
11059         }
11060         break;
11061     }
11062 }
11063
11064
11065 void
11066 DropMenuEvent(selection, x, y)
11067      ChessSquare selection;
11068      int x, y;
11069 {
11070     ChessMove moveType;
11071
11072     switch (gameMode) {
11073       case IcsPlayingWhite:
11074       case MachinePlaysBlack:
11075         if (!WhiteOnMove(currentMove)) {
11076             DisplayMoveError(_("It is Black's turn"));
11077             return;
11078         }
11079         moveType = WhiteDrop;
11080         break;
11081       case IcsPlayingBlack:
11082       case MachinePlaysWhite:
11083         if (WhiteOnMove(currentMove)) {
11084             DisplayMoveError(_("It is White's turn"));
11085             return;
11086         }
11087         moveType = BlackDrop;
11088         break;
11089       case EditGame:
11090         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11091         break;
11092       default:
11093         return;
11094     }
11095
11096     if (moveType == BlackDrop && selection < BlackPawn) {
11097       selection = (ChessSquare) ((int) selection
11098                                  + (int) BlackPawn - (int) WhitePawn);
11099     }
11100     if (boards[currentMove][y][x] != EmptySquare) {
11101         DisplayMoveError(_("That square is occupied"));
11102         return;
11103     }
11104
11105     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11106 }
11107
11108 void
11109 AcceptEvent()
11110 {
11111     /* Accept a pending offer of any kind from opponent */
11112     
11113     if (appData.icsActive) {
11114         SendToICS(ics_prefix);
11115         SendToICS("accept\n");
11116     } else if (cmailMsgLoaded) {
11117         if (currentMove == cmailOldMove &&
11118             commentList[cmailOldMove] != NULL &&
11119             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11120                    "Black offers a draw" : "White offers a draw")) {
11121             TruncateGame();
11122             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11123             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11124         } else {
11125             DisplayError(_("There is no pending offer on this move"), 0);
11126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11127         }
11128     } else {
11129         /* Not used for offers from chess program */
11130     }
11131 }
11132
11133 void
11134 DeclineEvent()
11135 {
11136     /* Decline a pending offer of any kind from opponent */
11137     
11138     if (appData.icsActive) {
11139         SendToICS(ics_prefix);
11140         SendToICS("decline\n");
11141     } else if (cmailMsgLoaded) {
11142         if (currentMove == cmailOldMove &&
11143             commentList[cmailOldMove] != NULL &&
11144             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11145                    "Black offers a draw" : "White offers a draw")) {
11146 #ifdef NOTDEF
11147             AppendComment(cmailOldMove, "Draw declined");
11148             DisplayComment(cmailOldMove - 1, "Draw declined");
11149 #endif /*NOTDEF*/
11150         } else {
11151             DisplayError(_("There is no pending offer on this move"), 0);
11152         }
11153     } else {
11154         /* Not used for offers from chess program */
11155     }
11156 }
11157
11158 void
11159 RematchEvent()
11160 {
11161     /* Issue ICS rematch command */
11162     if (appData.icsActive) {
11163         SendToICS(ics_prefix);
11164         SendToICS("rematch\n");
11165     }
11166 }
11167
11168 void
11169 CallFlagEvent()
11170 {
11171     /* Call your opponent's flag (claim a win on time) */
11172     if (appData.icsActive) {
11173         SendToICS(ics_prefix);
11174         SendToICS("flag\n");
11175     } else {
11176         switch (gameMode) {
11177           default:
11178             return;
11179           case MachinePlaysWhite:
11180             if (whiteFlag) {
11181                 if (blackFlag)
11182                   GameEnds(GameIsDrawn, "Both players ran out of time",
11183                            GE_PLAYER);
11184                 else
11185                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11186             } else {
11187                 DisplayError(_("Your opponent is not out of time"), 0);
11188             }
11189             break;
11190           case MachinePlaysBlack:
11191             if (blackFlag) {
11192                 if (whiteFlag)
11193                   GameEnds(GameIsDrawn, "Both players ran out of time",
11194                            GE_PLAYER);
11195                 else
11196                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11197             } else {
11198                 DisplayError(_("Your opponent is not out of time"), 0);
11199             }
11200             break;
11201         }
11202     }
11203 }
11204
11205 void
11206 DrawEvent()
11207 {
11208     /* Offer draw or accept pending draw offer from opponent */
11209     
11210     if (appData.icsActive) {
11211         /* Note: tournament rules require draw offers to be
11212            made after you make your move but before you punch
11213            your clock.  Currently ICS doesn't let you do that;
11214            instead, you immediately punch your clock after making
11215            a move, but you can offer a draw at any time. */
11216         
11217         SendToICS(ics_prefix);
11218         SendToICS("draw\n");
11219     } else if (cmailMsgLoaded) {
11220         if (currentMove == cmailOldMove &&
11221             commentList[cmailOldMove] != NULL &&
11222             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11223                    "Black offers a draw" : "White offers a draw")) {
11224             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11225             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11226         } else if (currentMove == cmailOldMove + 1) {
11227             char *offer = WhiteOnMove(cmailOldMove) ?
11228               "White offers a draw" : "Black offers a draw";
11229             AppendComment(currentMove, offer);
11230             DisplayComment(currentMove - 1, offer);
11231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11232         } else {
11233             DisplayError(_("You must make your move before offering a draw"), 0);
11234             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11235         }
11236     } else if (first.offeredDraw) {
11237         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11238     } else {
11239         if (first.sendDrawOffers) {
11240             SendToProgram("draw\n", &first);
11241             userOfferedDraw = TRUE;
11242         }
11243     }
11244 }
11245
11246 void
11247 AdjournEvent()
11248 {
11249     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11250     
11251     if (appData.icsActive) {
11252         SendToICS(ics_prefix);
11253         SendToICS("adjourn\n");
11254     } else {
11255         /* Currently GNU Chess doesn't offer or accept Adjourns */
11256     }
11257 }
11258
11259
11260 void
11261 AbortEvent()
11262 {
11263     /* Offer Abort or accept pending Abort offer from opponent */
11264     
11265     if (appData.icsActive) {
11266         SendToICS(ics_prefix);
11267         SendToICS("abort\n");
11268     } else {
11269         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11270     }
11271 }
11272
11273 void
11274 ResignEvent()
11275 {
11276     /* Resign.  You can do this even if it's not your turn. */
11277     
11278     if (appData.icsActive) {
11279         SendToICS(ics_prefix);
11280         SendToICS("resign\n");
11281     } else {
11282         switch (gameMode) {
11283           case MachinePlaysWhite:
11284             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11285             break;
11286           case MachinePlaysBlack:
11287             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11288             break;
11289           case EditGame:
11290             if (cmailMsgLoaded) {
11291                 TruncateGame();
11292                 if (WhiteOnMove(cmailOldMove)) {
11293                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11294                 } else {
11295                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11296                 }
11297                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11298             }
11299             break;
11300           default:
11301             break;
11302         }
11303     }
11304 }
11305
11306
11307 void
11308 StopObservingEvent()
11309 {
11310     /* Stop observing current games */
11311     SendToICS(ics_prefix);
11312     SendToICS("unobserve\n");
11313 }
11314
11315 void
11316 StopExaminingEvent()
11317 {
11318     /* Stop observing current game */
11319     SendToICS(ics_prefix);
11320     SendToICS("unexamine\n");
11321 }
11322
11323 void
11324 ForwardInner(target)
11325      int target;
11326 {
11327     int limit;
11328
11329     if (appData.debugMode)
11330         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11331                 target, currentMove, forwardMostMove);
11332
11333     if (gameMode == EditPosition)
11334       return;
11335
11336     if (gameMode == PlayFromGameFile && !pausing)
11337       PauseEvent();
11338     
11339     if (gameMode == IcsExamining && pausing)
11340       limit = pauseExamForwardMostMove;
11341     else
11342       limit = forwardMostMove;
11343     
11344     if (target > limit) target = limit;
11345
11346     if (target > 0 && moveList[target - 1][0]) {
11347         int fromX, fromY, toX, toY;
11348         toX = moveList[target - 1][2] - AAA;
11349         toY = moveList[target - 1][3] - ONE;
11350         if (moveList[target - 1][1] == '@') {
11351             if (appData.highlightLastMove) {
11352                 SetHighlights(-1, -1, toX, toY);
11353             }
11354         } else {
11355             fromX = moveList[target - 1][0] - AAA;
11356             fromY = moveList[target - 1][1] - ONE;
11357             if (target == currentMove + 1) {
11358                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11359             }
11360             if (appData.highlightLastMove) {
11361                 SetHighlights(fromX, fromY, toX, toY);
11362             }
11363         }
11364     }
11365     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11366         gameMode == Training || gameMode == PlayFromGameFile || 
11367         gameMode == AnalyzeFile) {
11368         while (currentMove < target) {
11369             SendMoveToProgram(currentMove++, &first);
11370         }
11371     } else {
11372         currentMove = target;
11373     }
11374     
11375     if (gameMode == EditGame || gameMode == EndOfGame) {
11376         whiteTimeRemaining = timeRemaining[0][currentMove];
11377         blackTimeRemaining = timeRemaining[1][currentMove];
11378     }
11379     DisplayBothClocks();
11380     DisplayMove(currentMove - 1);
11381     DrawPosition(FALSE, boards[currentMove]);
11382     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11383     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11384         DisplayComment(currentMove - 1, commentList[currentMove]);
11385     }
11386 }
11387
11388
11389 void
11390 ForwardEvent()
11391 {
11392     if (gameMode == IcsExamining && !pausing) {
11393         SendToICS(ics_prefix);
11394         SendToICS("forward\n");
11395     } else {
11396         ForwardInner(currentMove + 1);
11397     }
11398 }
11399
11400 void
11401 ToEndEvent()
11402 {
11403     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11404         /* to optimze, we temporarily turn off analysis mode while we feed
11405          * the remaining moves to the engine. Otherwise we get analysis output
11406          * after each move.
11407          */ 
11408         if (first.analysisSupport) {
11409           SendToProgram("exit\nforce\n", &first);
11410           first.analyzing = FALSE;
11411         }
11412     }
11413         
11414     if (gameMode == IcsExamining && !pausing) {
11415         SendToICS(ics_prefix);
11416         SendToICS("forward 999999\n");
11417     } else {
11418         ForwardInner(forwardMostMove);
11419     }
11420
11421     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11422         /* we have fed all the moves, so reactivate analysis mode */
11423         SendToProgram("analyze\n", &first);
11424         first.analyzing = TRUE;
11425         /*first.maybeThinking = TRUE;*/
11426         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11427     }
11428 }
11429
11430 void
11431 BackwardInner(target)
11432      int target;
11433 {
11434     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11435
11436     if (appData.debugMode)
11437         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11438                 target, currentMove, forwardMostMove);
11439
11440     if (gameMode == EditPosition) return;
11441     if (currentMove <= backwardMostMove) {
11442         ClearHighlights();
11443         DrawPosition(full_redraw, boards[currentMove]);
11444         return;
11445     }
11446     if (gameMode == PlayFromGameFile && !pausing)
11447       PauseEvent();
11448     
11449     if (moveList[target][0]) {
11450         int fromX, fromY, toX, toY;
11451         toX = moveList[target][2] - AAA;
11452         toY = moveList[target][3] - ONE;
11453         if (moveList[target][1] == '@') {
11454             if (appData.highlightLastMove) {
11455                 SetHighlights(-1, -1, toX, toY);
11456             }
11457         } else {
11458             fromX = moveList[target][0] - AAA;
11459             fromY = moveList[target][1] - ONE;
11460             if (target == currentMove - 1) {
11461                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11462             }
11463             if (appData.highlightLastMove) {
11464                 SetHighlights(fromX, fromY, toX, toY);
11465             }
11466         }
11467     }
11468     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11469         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11470         while (currentMove > target) {
11471             SendToProgram("undo\n", &first);
11472             currentMove--;
11473         }
11474     } else {
11475         currentMove = target;
11476     }
11477     
11478     if (gameMode == EditGame || gameMode == EndOfGame) {
11479         whiteTimeRemaining = timeRemaining[0][currentMove];
11480         blackTimeRemaining = timeRemaining[1][currentMove];
11481     }
11482     DisplayBothClocks();
11483     DisplayMove(currentMove - 1);
11484     DrawPosition(full_redraw, boards[currentMove]);
11485     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11486     // [HGM] PV info: routine tests if comment empty
11487     DisplayComment(currentMove - 1, commentList[currentMove]);
11488 }
11489
11490 void
11491 BackwardEvent()
11492 {
11493     if (gameMode == IcsExamining && !pausing) {
11494         SendToICS(ics_prefix);
11495         SendToICS("backward\n");
11496     } else {
11497         BackwardInner(currentMove - 1);
11498     }
11499 }
11500
11501 void
11502 ToStartEvent()
11503 {
11504     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11505         /* to optimze, we temporarily turn off analysis mode while we undo
11506          * all the moves. Otherwise we get analysis output after each undo.
11507          */ 
11508         if (first.analysisSupport) {
11509           SendToProgram("exit\nforce\n", &first);
11510           first.analyzing = FALSE;
11511         }
11512     }
11513
11514     if (gameMode == IcsExamining && !pausing) {
11515         SendToICS(ics_prefix);
11516         SendToICS("backward 999999\n");
11517     } else {
11518         BackwardInner(backwardMostMove);
11519     }
11520
11521     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11522         /* we have fed all the moves, so reactivate analysis mode */
11523         SendToProgram("analyze\n", &first);
11524         first.analyzing = TRUE;
11525         /*first.maybeThinking = TRUE;*/
11526         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11527     }
11528 }
11529
11530 void
11531 ToNrEvent(int to)
11532 {
11533   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11534   if (to >= forwardMostMove) to = forwardMostMove;
11535   if (to <= backwardMostMove) to = backwardMostMove;
11536   if (to < currentMove) {
11537     BackwardInner(to);
11538   } else {
11539     ForwardInner(to);
11540   }
11541 }
11542
11543 void
11544 RevertEvent()
11545 {
11546     if (gameMode != IcsExamining) {
11547         DisplayError(_("You are not examining a game"), 0);
11548         return;
11549     }
11550     if (pausing) {
11551         DisplayError(_("You can't revert while pausing"), 0);
11552         return;
11553     }
11554     SendToICS(ics_prefix);
11555     SendToICS("revert\n");
11556 }
11557
11558 void
11559 RetractMoveEvent()
11560 {
11561     switch (gameMode) {
11562       case MachinePlaysWhite:
11563       case MachinePlaysBlack:
11564         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11565             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11566             return;
11567         }
11568         if (forwardMostMove < 2) return;
11569         currentMove = forwardMostMove = forwardMostMove - 2;
11570         whiteTimeRemaining = timeRemaining[0][currentMove];
11571         blackTimeRemaining = timeRemaining[1][currentMove];
11572         DisplayBothClocks();
11573         DisplayMove(currentMove - 1);
11574         ClearHighlights();/*!! could figure this out*/
11575         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11576         SendToProgram("remove\n", &first);
11577         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11578         break;
11579
11580       case BeginningOfGame:
11581       default:
11582         break;
11583
11584       case IcsPlayingWhite:
11585       case IcsPlayingBlack:
11586         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11587             SendToICS(ics_prefix);
11588             SendToICS("takeback 2\n");
11589         } else {
11590             SendToICS(ics_prefix);
11591             SendToICS("takeback 1\n");
11592         }
11593         break;
11594     }
11595 }
11596
11597 void
11598 MoveNowEvent()
11599 {
11600     ChessProgramState *cps;
11601
11602     switch (gameMode) {
11603       case MachinePlaysWhite:
11604         if (!WhiteOnMove(forwardMostMove)) {
11605             DisplayError(_("It is your turn"), 0);
11606             return;
11607         }
11608         cps = &first;
11609         break;
11610       case MachinePlaysBlack:
11611         if (WhiteOnMove(forwardMostMove)) {
11612             DisplayError(_("It is your turn"), 0);
11613             return;
11614         }
11615         cps = &first;
11616         break;
11617       case TwoMachinesPlay:
11618         if (WhiteOnMove(forwardMostMove) ==
11619             (first.twoMachinesColor[0] == 'w')) {
11620             cps = &first;
11621         } else {
11622             cps = &second;
11623         }
11624         break;
11625       case BeginningOfGame:
11626       default:
11627         return;
11628     }
11629     SendToProgram("?\n", cps);
11630 }
11631
11632 void
11633 TruncateGameEvent()
11634 {
11635     EditGameEvent();
11636     if (gameMode != EditGame) return;
11637     TruncateGame();
11638 }
11639
11640 void
11641 TruncateGame()
11642 {
11643     if (forwardMostMove > currentMove) {
11644         if (gameInfo.resultDetails != NULL) {
11645             free(gameInfo.resultDetails);
11646             gameInfo.resultDetails = NULL;
11647             gameInfo.result = GameUnfinished;
11648         }
11649         forwardMostMove = currentMove;
11650         HistorySet(parseList, backwardMostMove, forwardMostMove,
11651                    currentMove-1);
11652     }
11653 }
11654
11655 void
11656 HintEvent()
11657 {
11658     if (appData.noChessProgram) return;
11659     switch (gameMode) {
11660       case MachinePlaysWhite:
11661         if (WhiteOnMove(forwardMostMove)) {
11662             DisplayError(_("Wait until your turn"), 0);
11663             return;
11664         }
11665         break;
11666       case BeginningOfGame:
11667       case MachinePlaysBlack:
11668         if (!WhiteOnMove(forwardMostMove)) {
11669             DisplayError(_("Wait until your turn"), 0);
11670             return;
11671         }
11672         break;
11673       default:
11674         DisplayError(_("No hint available"), 0);
11675         return;
11676     }
11677     SendToProgram("hint\n", &first);
11678     hintRequested = TRUE;
11679 }
11680
11681 void
11682 BookEvent()
11683 {
11684     if (appData.noChessProgram) return;
11685     switch (gameMode) {
11686       case MachinePlaysWhite:
11687         if (WhiteOnMove(forwardMostMove)) {
11688             DisplayError(_("Wait until your turn"), 0);
11689             return;
11690         }
11691         break;
11692       case BeginningOfGame:
11693       case MachinePlaysBlack:
11694         if (!WhiteOnMove(forwardMostMove)) {
11695             DisplayError(_("Wait until your turn"), 0);
11696             return;
11697         }
11698         break;
11699       case EditPosition:
11700         EditPositionDone();
11701         break;
11702       case TwoMachinesPlay:
11703         return;
11704       default:
11705         break;
11706     }
11707     SendToProgram("bk\n", &first);
11708     bookOutput[0] = NULLCHAR;
11709     bookRequested = TRUE;
11710 }
11711
11712 void
11713 AboutGameEvent()
11714 {
11715     char *tags = PGNTags(&gameInfo);
11716     TagsPopUp(tags, CmailMsg());
11717     free(tags);
11718 }
11719
11720 /* end button procedures */
11721
11722 void
11723 PrintPosition(fp, move)
11724      FILE *fp;
11725      int move;
11726 {
11727     int i, j;
11728     
11729     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11730         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11731             char c = PieceToChar(boards[move][i][j]);
11732             fputc(c == 'x' ? '.' : c, fp);
11733             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11734         }
11735     }
11736     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11737       fprintf(fp, "white to play\n");
11738     else
11739       fprintf(fp, "black to play\n");
11740 }
11741
11742 void
11743 PrintOpponents(fp)
11744      FILE *fp;
11745 {
11746     if (gameInfo.white != NULL) {
11747         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11748     } else {
11749         fprintf(fp, "\n");
11750     }
11751 }
11752
11753 /* Find last component of program's own name, using some heuristics */
11754 void
11755 TidyProgramName(prog, host, buf)
11756      char *prog, *host, buf[MSG_SIZ];
11757 {
11758     char *p, *q;
11759     int local = (strcmp(host, "localhost") == 0);
11760     while (!local && (p = strchr(prog, ';')) != NULL) {
11761         p++;
11762         while (*p == ' ') p++;
11763         prog = p;
11764     }
11765     if (*prog == '"' || *prog == '\'') {
11766         q = strchr(prog + 1, *prog);
11767     } else {
11768         q = strchr(prog, ' ');
11769     }
11770     if (q == NULL) q = prog + strlen(prog);
11771     p = q;
11772     while (p >= prog && *p != '/' && *p != '\\') p--;
11773     p++;
11774     if(p == prog && *p == '"') p++;
11775     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11776     memcpy(buf, p, q - p);
11777     buf[q - p] = NULLCHAR;
11778     if (!local) {
11779         strcat(buf, "@");
11780         strcat(buf, host);
11781     }
11782 }
11783
11784 char *
11785 TimeControlTagValue()
11786 {
11787     char buf[MSG_SIZ];
11788     if (!appData.clockMode) {
11789         strcpy(buf, "-");
11790     } else if (movesPerSession > 0) {
11791         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11792     } else if (timeIncrement == 0) {
11793         sprintf(buf, "%ld", timeControl/1000);
11794     } else {
11795         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11796     }
11797     return StrSave(buf);
11798 }
11799
11800 void
11801 SetGameInfo()
11802 {
11803     /* This routine is used only for certain modes */
11804     VariantClass v = gameInfo.variant;
11805     ClearGameInfo(&gameInfo);
11806     gameInfo.variant = v;
11807
11808     switch (gameMode) {
11809       case MachinePlaysWhite:
11810         gameInfo.event = StrSave( appData.pgnEventHeader );
11811         gameInfo.site = StrSave(HostName());
11812         gameInfo.date = PGNDate();
11813         gameInfo.round = StrSave("-");
11814         gameInfo.white = StrSave(first.tidy);
11815         gameInfo.black = StrSave(UserName());
11816         gameInfo.timeControl = TimeControlTagValue();
11817         break;
11818
11819       case MachinePlaysBlack:
11820         gameInfo.event = StrSave( appData.pgnEventHeader );
11821         gameInfo.site = StrSave(HostName());
11822         gameInfo.date = PGNDate();
11823         gameInfo.round = StrSave("-");
11824         gameInfo.white = StrSave(UserName());
11825         gameInfo.black = StrSave(first.tidy);
11826         gameInfo.timeControl = TimeControlTagValue();
11827         break;
11828
11829       case TwoMachinesPlay:
11830         gameInfo.event = StrSave( appData.pgnEventHeader );
11831         gameInfo.site = StrSave(HostName());
11832         gameInfo.date = PGNDate();
11833         if (matchGame > 0) {
11834             char buf[MSG_SIZ];
11835             sprintf(buf, "%d", matchGame);
11836             gameInfo.round = StrSave(buf);
11837         } else {
11838             gameInfo.round = StrSave("-");
11839         }
11840         if (first.twoMachinesColor[0] == 'w') {
11841             gameInfo.white = StrSave(first.tidy);
11842             gameInfo.black = StrSave(second.tidy);
11843         } else {
11844             gameInfo.white = StrSave(second.tidy);
11845             gameInfo.black = StrSave(first.tidy);
11846         }
11847         gameInfo.timeControl = TimeControlTagValue();
11848         break;
11849
11850       case EditGame:
11851         gameInfo.event = StrSave("Edited game");
11852         gameInfo.site = StrSave(HostName());
11853         gameInfo.date = PGNDate();
11854         gameInfo.round = StrSave("-");
11855         gameInfo.white = StrSave("-");
11856         gameInfo.black = StrSave("-");
11857         break;
11858
11859       case EditPosition:
11860         gameInfo.event = StrSave("Edited position");
11861         gameInfo.site = StrSave(HostName());
11862         gameInfo.date = PGNDate();
11863         gameInfo.round = StrSave("-");
11864         gameInfo.white = StrSave("-");
11865         gameInfo.black = StrSave("-");
11866         break;
11867
11868       case IcsPlayingWhite:
11869       case IcsPlayingBlack:
11870       case IcsObserving:
11871       case IcsExamining:
11872         break;
11873
11874       case PlayFromGameFile:
11875         gameInfo.event = StrSave("Game from non-PGN file");
11876         gameInfo.site = StrSave(HostName());
11877         gameInfo.date = PGNDate();
11878         gameInfo.round = StrSave("-");
11879         gameInfo.white = StrSave("?");
11880         gameInfo.black = StrSave("?");
11881         break;
11882
11883       default:
11884         break;
11885     }
11886 }
11887
11888 void
11889 ReplaceComment(index, text)
11890      int index;
11891      char *text;
11892 {
11893     int len;
11894
11895     while (*text == '\n') text++;
11896     len = strlen(text);
11897     while (len > 0 && text[len - 1] == '\n') len--;
11898
11899     if (commentList[index] != NULL)
11900       free(commentList[index]);
11901
11902     if (len == 0) {
11903         commentList[index] = NULL;
11904         return;
11905     }
11906     commentList[index] = (char *) malloc(len + 2);
11907     strncpy(commentList[index], text, len);
11908     commentList[index][len] = '\n';
11909     commentList[index][len + 1] = NULLCHAR;
11910 }
11911
11912 void
11913 CrushCRs(text)
11914      char *text;
11915 {
11916   char *p = text;
11917   char *q = text;
11918   char ch;
11919
11920   do {
11921     ch = *p++;
11922     if (ch == '\r') continue;
11923     *q++ = ch;
11924   } while (ch != '\0');
11925 }
11926
11927 void
11928 AppendComment(index, text)
11929      int index;
11930      char *text;
11931 {
11932     int oldlen, len;
11933     char *old;
11934
11935     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11936
11937     CrushCRs(text);
11938     while (*text == '\n') text++;
11939     len = strlen(text);
11940     while (len > 0 && text[len - 1] == '\n') len--;
11941
11942     if (len == 0) return;
11943
11944     if (commentList[index] != NULL) {
11945         old = commentList[index];
11946         oldlen = strlen(old);
11947         commentList[index] = (char *) malloc(oldlen + len + 2);
11948         strcpy(commentList[index], old);
11949         free(old);
11950         strncpy(&commentList[index][oldlen], text, len);
11951         commentList[index][oldlen + len] = '\n';
11952         commentList[index][oldlen + len + 1] = NULLCHAR;
11953     } else {
11954         commentList[index] = (char *) malloc(len + 2);
11955         strncpy(commentList[index], text, len);
11956         commentList[index][len] = '\n';
11957         commentList[index][len + 1] = NULLCHAR;
11958     }
11959 }
11960
11961 static char * FindStr( char * text, char * sub_text )
11962 {
11963     char * result = strstr( text, sub_text );
11964
11965     if( result != NULL ) {
11966         result += strlen( sub_text );
11967     }
11968
11969     return result;
11970 }
11971
11972 /* [AS] Try to extract PV info from PGN comment */
11973 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11974 char *GetInfoFromComment( int index, char * text )
11975 {
11976     char * sep = text;
11977
11978     if( text != NULL && index > 0 ) {
11979         int score = 0;
11980         int depth = 0;
11981         int time = -1, sec = 0, deci;
11982         char * s_eval = FindStr( text, "[%eval " );
11983         char * s_emt = FindStr( text, "[%emt " );
11984
11985         if( s_eval != NULL || s_emt != NULL ) {
11986             /* New style */
11987             char delim;
11988
11989             if( s_eval != NULL ) {
11990                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11991                     return text;
11992                 }
11993
11994                 if( delim != ']' ) {
11995                     return text;
11996                 }
11997             }
11998
11999             if( s_emt != NULL ) {
12000             }
12001         }
12002         else {
12003             /* We expect something like: [+|-]nnn.nn/dd */
12004             int score_lo = 0;
12005
12006             sep = strchr( text, '/' );
12007             if( sep == NULL || sep < (text+4) ) {
12008                 return text;
12009             }
12010
12011             time = -1; sec = -1; deci = -1;
12012             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12013                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12014                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12015                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12016                 return text;
12017             }
12018
12019             if( score_lo < 0 || score_lo >= 100 ) {
12020                 return text;
12021             }
12022
12023             if(sec >= 0) time = 600*time + 10*sec; else
12024             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12025
12026             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12027
12028             /* [HGM] PV time: now locate end of PV info */
12029             while( *++sep >= '0' && *sep <= '9'); // strip depth
12030             if(time >= 0)
12031             while( *++sep >= '0' && *sep <= '9'); // strip time
12032             if(sec >= 0)
12033             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12034             if(deci >= 0)
12035             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12036             while(*sep == ' ') sep++;
12037         }
12038
12039         if( depth <= 0 ) {
12040             return text;
12041         }
12042
12043         if( time < 0 ) {
12044             time = -1;
12045         }
12046
12047         pvInfoList[index-1].depth = depth;
12048         pvInfoList[index-1].score = score;
12049         pvInfoList[index-1].time  = 10*time; // centi-sec
12050     }
12051     return sep;
12052 }
12053
12054 void
12055 SendToProgram(message, cps)
12056      char *message;
12057      ChessProgramState *cps;
12058 {
12059     int count, outCount, error;
12060     char buf[MSG_SIZ];
12061
12062     if (cps->pr == NULL) return;
12063     Attention(cps);
12064     
12065     if (appData.debugMode) {
12066         TimeMark now;
12067         GetTimeMark(&now);
12068         fprintf(debugFP, "%ld >%-6s: %s", 
12069                 SubtractTimeMarks(&now, &programStartTime),
12070                 cps->which, message);
12071     }
12072     
12073     count = strlen(message);
12074     outCount = OutputToProcess(cps->pr, message, count, &error);
12075     if (outCount < count && !exiting 
12076                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12077         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12078         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12079             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12080                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12081                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12082             } else {
12083                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12084             }
12085             gameInfo.resultDetails = buf;
12086         }
12087         DisplayFatalError(buf, error, 1);
12088     }
12089 }
12090
12091 void
12092 ReceiveFromProgram(isr, closure, message, count, error)
12093      InputSourceRef isr;
12094      VOIDSTAR closure;
12095      char *message;
12096      int count;
12097      int error;
12098 {
12099     char *end_str;
12100     char buf[MSG_SIZ];
12101     ChessProgramState *cps = (ChessProgramState *)closure;
12102
12103     if (isr != cps->isr) return; /* Killed intentionally */
12104     if (count <= 0) {
12105         if (count == 0) {
12106             sprintf(buf,
12107                     _("Error: %s chess program (%s) exited unexpectedly"),
12108                     cps->which, cps->program);
12109         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12110                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12111                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12112                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12113                 } else {
12114                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12115                 }
12116                 gameInfo.resultDetails = buf;
12117             }
12118             RemoveInputSource(cps->isr);
12119             DisplayFatalError(buf, 0, 1);
12120         } else {
12121             sprintf(buf,
12122                     _("Error reading from %s chess program (%s)"),
12123                     cps->which, cps->program);
12124             RemoveInputSource(cps->isr);
12125
12126             /* [AS] Program is misbehaving badly... kill it */
12127             if( count == -2 ) {
12128                 DestroyChildProcess( cps->pr, 9 );
12129                 cps->pr = NoProc;
12130             }
12131
12132             DisplayFatalError(buf, error, 1);
12133         }
12134         return;
12135     }
12136     
12137     if ((end_str = strchr(message, '\r')) != NULL)
12138       *end_str = NULLCHAR;
12139     if ((end_str = strchr(message, '\n')) != NULL)
12140       *end_str = NULLCHAR;
12141     
12142     if (appData.debugMode) {
12143         TimeMark now; int print = 1;
12144         char *quote = ""; char c; int i;
12145
12146         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12147                 char start = message[0];
12148                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12149                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12150                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12151                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12152                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12153                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12154                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12155                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12156                         { quote = "# "; print = (appData.engineComments == 2); }
12157                 message[0] = start; // restore original message
12158         }
12159         if(print) {
12160                 GetTimeMark(&now);
12161                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12162                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12163                         quote,
12164                         message);
12165         }
12166     }
12167
12168     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12169     if (appData.icsEngineAnalyze) {
12170         if (strstr(message, "whisper") != NULL ||
12171              strstr(message, "kibitz") != NULL || 
12172             strstr(message, "tellics") != NULL) return;
12173     }
12174
12175     HandleMachineMove(message, cps);
12176 }
12177
12178
12179 void
12180 SendTimeControl(cps, mps, tc, inc, sd, st)
12181      ChessProgramState *cps;
12182      int mps, inc, sd, st;
12183      long tc;
12184 {
12185     char buf[MSG_SIZ];
12186     int seconds;
12187
12188     if( timeControl_2 > 0 ) {
12189         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12190             tc = timeControl_2;
12191         }
12192     }
12193     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12194     inc /= cps->timeOdds;
12195     st  /= cps->timeOdds;
12196
12197     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12198
12199     if (st > 0) {
12200       /* Set exact time per move, normally using st command */
12201       if (cps->stKludge) {
12202         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12203         seconds = st % 60;
12204         if (seconds == 0) {
12205           sprintf(buf, "level 1 %d\n", st/60);
12206         } else {
12207           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12208         }
12209       } else {
12210         sprintf(buf, "st %d\n", st);
12211       }
12212     } else {
12213       /* Set conventional or incremental time control, using level command */
12214       if (seconds == 0) {
12215         /* Note old gnuchess bug -- minutes:seconds used to not work.
12216            Fixed in later versions, but still avoid :seconds
12217            when seconds is 0. */
12218         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12219       } else {
12220         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12221                 seconds, inc/1000);
12222       }
12223     }
12224     SendToProgram(buf, cps);
12225
12226     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12227     /* Orthogonally, limit search to given depth */
12228     if (sd > 0) {
12229       if (cps->sdKludge) {
12230         sprintf(buf, "depth\n%d\n", sd);
12231       } else {
12232         sprintf(buf, "sd %d\n", sd);
12233       }
12234       SendToProgram(buf, cps);
12235     }
12236
12237     if(cps->nps > 0) { /* [HGM] nps */
12238         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12239         else {
12240                 sprintf(buf, "nps %d\n", cps->nps);
12241               SendToProgram(buf, cps);
12242         }
12243     }
12244 }
12245
12246 ChessProgramState *WhitePlayer()
12247 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12248 {
12249     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12250        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12251         return &second;
12252     return &first;
12253 }
12254
12255 void
12256 SendTimeRemaining(cps, machineWhite)
12257      ChessProgramState *cps;
12258      int /*boolean*/ machineWhite;
12259 {
12260     char message[MSG_SIZ];
12261     long time, otime;
12262
12263     /* Note: this routine must be called when the clocks are stopped
12264        or when they have *just* been set or switched; otherwise
12265        it will be off by the time since the current tick started.
12266     */
12267     if (machineWhite) {
12268         time = whiteTimeRemaining / 10;
12269         otime = blackTimeRemaining / 10;
12270     } else {
12271         time = blackTimeRemaining / 10;
12272         otime = whiteTimeRemaining / 10;
12273     }
12274     /* [HGM] translate opponent's time by time-odds factor */
12275     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12276     if (appData.debugMode) {
12277         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12278     }
12279
12280     if (time <= 0) time = 1;
12281     if (otime <= 0) otime = 1;
12282     
12283     sprintf(message, "time %ld\n", time);
12284     SendToProgram(message, cps);
12285
12286     sprintf(message, "otim %ld\n", otime);
12287     SendToProgram(message, cps);
12288 }
12289
12290 int
12291 BoolFeature(p, name, loc, cps)
12292      char **p;
12293      char *name;
12294      int *loc;
12295      ChessProgramState *cps;
12296 {
12297   char buf[MSG_SIZ];
12298   int len = strlen(name);
12299   int val;
12300   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12301     (*p) += len + 1;
12302     sscanf(*p, "%d", &val);
12303     *loc = (val != 0);
12304     while (**p && **p != ' ') (*p)++;
12305     sprintf(buf, "accepted %s\n", name);
12306     SendToProgram(buf, cps);
12307     return TRUE;
12308   }
12309   return FALSE;
12310 }
12311
12312 int
12313 IntFeature(p, name, loc, cps)
12314      char **p;
12315      char *name;
12316      int *loc;
12317      ChessProgramState *cps;
12318 {
12319   char buf[MSG_SIZ];
12320   int len = strlen(name);
12321   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12322     (*p) += len + 1;
12323     sscanf(*p, "%d", loc);
12324     while (**p && **p != ' ') (*p)++;
12325     sprintf(buf, "accepted %s\n", name);
12326     SendToProgram(buf, cps);
12327     return TRUE;
12328   }
12329   return FALSE;
12330 }
12331
12332 int
12333 StringFeature(p, name, loc, cps)
12334      char **p;
12335      char *name;
12336      char loc[];
12337      ChessProgramState *cps;
12338 {
12339   char buf[MSG_SIZ];
12340   int len = strlen(name);
12341   if (strncmp((*p), name, len) == 0
12342       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12343     (*p) += len + 2;
12344     sscanf(*p, "%[^\"]", loc);
12345     while (**p && **p != '\"') (*p)++;
12346     if (**p == '\"') (*p)++;
12347     sprintf(buf, "accepted %s\n", name);
12348     SendToProgram(buf, cps);
12349     return TRUE;
12350   }
12351   return FALSE;
12352 }
12353
12354 int 
12355 ParseOption(Option *opt, ChessProgramState *cps)
12356 // [HGM] options: process the string that defines an engine option, and determine
12357 // name, type, default value, and allowed value range
12358 {
12359         char *p, *q, buf[MSG_SIZ];
12360         int n, min = (-1)<<31, max = 1<<31, def;
12361
12362         if(p = strstr(opt->name, " -spin ")) {
12363             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12364             if(max < min) max = min; // enforce consistency
12365             if(def < min) def = min;
12366             if(def > max) def = max;
12367             opt->value = def;
12368             opt->min = min;
12369             opt->max = max;
12370             opt->type = Spin;
12371         } else if((p = strstr(opt->name, " -slider "))) {
12372             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12373             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12374             if(max < min) max = min; // enforce consistency
12375             if(def < min) def = min;
12376             if(def > max) def = max;
12377             opt->value = def;
12378             opt->min = min;
12379             opt->max = max;
12380             opt->type = Spin; // Slider;
12381         } else if((p = strstr(opt->name, " -string "))) {
12382             opt->textValue = p+9;
12383             opt->type = TextBox;
12384         } else if((p = strstr(opt->name, " -file "))) {
12385             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12386             opt->textValue = p+7;
12387             opt->type = TextBox; // FileName;
12388         } else if((p = strstr(opt->name, " -path "))) {
12389             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12390             opt->textValue = p+7;
12391             opt->type = TextBox; // PathName;
12392         } else if(p = strstr(opt->name, " -check ")) {
12393             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12394             opt->value = (def != 0);
12395             opt->type = CheckBox;
12396         } else if(p = strstr(opt->name, " -combo ")) {
12397             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12398             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12399             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12400             opt->value = n = 0;
12401             while(q = StrStr(q, " /// ")) {
12402                 n++; *q = 0;    // count choices, and null-terminate each of them
12403                 q += 5;
12404                 if(*q == '*') { // remember default, which is marked with * prefix
12405                     q++;
12406                     opt->value = n;
12407                 }
12408                 cps->comboList[cps->comboCnt++] = q;
12409             }
12410             cps->comboList[cps->comboCnt++] = NULL;
12411             opt->max = n + 1;
12412             opt->type = ComboBox;
12413         } else if(p = strstr(opt->name, " -button")) {
12414             opt->type = Button;
12415         } else if(p = strstr(opt->name, " -save")) {
12416             opt->type = SaveButton;
12417         } else return FALSE;
12418         *p = 0; // terminate option name
12419         // now look if the command-line options define a setting for this engine option.
12420         if(cps->optionSettings && cps->optionSettings[0])
12421             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12422         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12423                 sprintf(buf, "option %s", p);
12424                 if(p = strstr(buf, ",")) *p = 0;
12425                 strcat(buf, "\n");
12426                 SendToProgram(buf, cps);
12427         }
12428         return TRUE;
12429 }
12430
12431 void
12432 FeatureDone(cps, val)
12433      ChessProgramState* cps;
12434      int val;
12435 {
12436   DelayedEventCallback cb = GetDelayedEvent();
12437   if ((cb == InitBackEnd3 && cps == &first) ||
12438       (cb == TwoMachinesEventIfReady && cps == &second)) {
12439     CancelDelayedEvent();
12440     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12441   }
12442   cps->initDone = val;
12443 }
12444
12445 /* Parse feature command from engine */
12446 void
12447 ParseFeatures(args, cps)
12448      char* args;
12449      ChessProgramState *cps;  
12450 {
12451   char *p = args;
12452   char *q;
12453   int val;
12454   char buf[MSG_SIZ];
12455
12456   for (;;) {
12457     while (*p == ' ') p++;
12458     if (*p == NULLCHAR) return;
12459
12460     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12461     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12462     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12463     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12464     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12465     if (BoolFeature(&p, "reuse", &val, cps)) {
12466       /* Engine can disable reuse, but can't enable it if user said no */
12467       if (!val) cps->reuse = FALSE;
12468       continue;
12469     }
12470     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12471     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12472       if (gameMode == TwoMachinesPlay) {
12473         DisplayTwoMachinesTitle();
12474       } else {
12475         DisplayTitle("");
12476       }
12477       continue;
12478     }
12479     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12480     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12481     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12482     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12483     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12484     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12485     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12486     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12487     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12488     if (IntFeature(&p, "done", &val, cps)) {
12489       FeatureDone(cps, val);
12490       continue;
12491     }
12492     /* Added by Tord: */
12493     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12494     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12495     /* End of additions by Tord */
12496
12497     /* [HGM] added features: */
12498     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12499     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12500     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12501     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12502     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12503     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12504     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12505         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12506             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12507             SendToProgram(buf, cps);
12508             continue;
12509         }
12510         if(cps->nrOptions >= MAX_OPTIONS) {
12511             cps->nrOptions--;
12512             sprintf(buf, "%s engine has too many options\n", cps->which);
12513             DisplayError(buf, 0);
12514         }
12515         continue;
12516     }
12517     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12518     /* End of additions by HGM */
12519
12520     /* unknown feature: complain and skip */
12521     q = p;
12522     while (*q && *q != '=') q++;
12523     sprintf(buf, "rejected %.*s\n", q-p, p);
12524     SendToProgram(buf, cps);
12525     p = q;
12526     if (*p == '=') {
12527       p++;
12528       if (*p == '\"') {
12529         p++;
12530         while (*p && *p != '\"') p++;
12531         if (*p == '\"') p++;
12532       } else {
12533         while (*p && *p != ' ') p++;
12534       }
12535     }
12536   }
12537
12538 }
12539
12540 void
12541 PeriodicUpdatesEvent(newState)
12542      int newState;
12543 {
12544     if (newState == appData.periodicUpdates)
12545       return;
12546
12547     appData.periodicUpdates=newState;
12548
12549     /* Display type changes, so update it now */
12550     DisplayAnalysis();
12551
12552     /* Get the ball rolling again... */
12553     if (newState) {
12554         AnalysisPeriodicEvent(1);
12555         StartAnalysisClock();
12556     }
12557 }
12558
12559 void
12560 PonderNextMoveEvent(newState)
12561      int newState;
12562 {
12563     if (newState == appData.ponderNextMove) return;
12564     if (gameMode == EditPosition) EditPositionDone();
12565     if (newState) {
12566         SendToProgram("hard\n", &first);
12567         if (gameMode == TwoMachinesPlay) {
12568             SendToProgram("hard\n", &second);
12569         }
12570     } else {
12571         SendToProgram("easy\n", &first);
12572         thinkOutput[0] = NULLCHAR;
12573         if (gameMode == TwoMachinesPlay) {
12574             SendToProgram("easy\n", &second);
12575         }
12576     }
12577     appData.ponderNextMove = newState;
12578 }
12579
12580 void
12581 NewSettingEvent(option, command, value)
12582      char *command;
12583      int option, value;
12584 {
12585     char buf[MSG_SIZ];
12586
12587     if (gameMode == EditPosition) EditPositionDone();
12588     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12589     SendToProgram(buf, &first);
12590     if (gameMode == TwoMachinesPlay) {
12591         SendToProgram(buf, &second);
12592     }
12593 }
12594
12595 void
12596 ShowThinkingEvent()
12597 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12598 {
12599     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12600     int newState = appData.showThinking
12601         // [HGM] thinking: other features now need thinking output as well
12602         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12603     
12604     if (oldState == newState) return;
12605     oldState = newState;
12606     if (gameMode == EditPosition) EditPositionDone();
12607     if (oldState) {
12608         SendToProgram("post\n", &first);
12609         if (gameMode == TwoMachinesPlay) {
12610             SendToProgram("post\n", &second);
12611         }
12612     } else {
12613         SendToProgram("nopost\n", &first);
12614         thinkOutput[0] = NULLCHAR;
12615         if (gameMode == TwoMachinesPlay) {
12616             SendToProgram("nopost\n", &second);
12617         }
12618     }
12619 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12620 }
12621
12622 void
12623 AskQuestionEvent(title, question, replyPrefix, which)
12624      char *title; char *question; char *replyPrefix; char *which;
12625 {
12626   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12627   if (pr == NoProc) return;
12628   AskQuestion(title, question, replyPrefix, pr);
12629 }
12630
12631 void
12632 DisplayMove(moveNumber)
12633      int moveNumber;
12634 {
12635     char message[MSG_SIZ];
12636     char res[MSG_SIZ];
12637     char cpThinkOutput[MSG_SIZ];
12638
12639     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12640     
12641     if (moveNumber == forwardMostMove - 1 || 
12642         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12643
12644         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12645
12646         if (strchr(cpThinkOutput, '\n')) {
12647             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12648         }
12649     } else {
12650         *cpThinkOutput = NULLCHAR;
12651     }
12652
12653     /* [AS] Hide thinking from human user */
12654     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12655         *cpThinkOutput = NULLCHAR;
12656         if( thinkOutput[0] != NULLCHAR ) {
12657             int i;
12658
12659             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12660                 cpThinkOutput[i] = '.';
12661             }
12662             cpThinkOutput[i] = NULLCHAR;
12663             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12664         }
12665     }
12666
12667     if (moveNumber == forwardMostMove - 1 &&
12668         gameInfo.resultDetails != NULL) {
12669         if (gameInfo.resultDetails[0] == NULLCHAR) {
12670             sprintf(res, " %s", PGNResult(gameInfo.result));
12671         } else {
12672             sprintf(res, " {%s} %s",
12673                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12674         }
12675     } else {
12676         res[0] = NULLCHAR;
12677     }
12678
12679     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12680         DisplayMessage(res, cpThinkOutput);
12681     } else {
12682         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12683                 WhiteOnMove(moveNumber) ? " " : ".. ",
12684                 parseList[moveNumber], res);
12685         DisplayMessage(message, cpThinkOutput);
12686     }
12687 }
12688
12689 void
12690 DisplayAnalysisText(text)
12691      char *text;
12692 {
12693     char buf[MSG_SIZ];
12694
12695     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12696                || appData.icsEngineAnalyze) {
12697         sprintf(buf, "Analysis (%s)", first.tidy);
12698         AnalysisPopUp(buf, text);
12699     }
12700 }
12701
12702 static int
12703 only_one_move(str)
12704      char *str;
12705 {
12706     while (*str && isspace(*str)) ++str;
12707     while (*str && !isspace(*str)) ++str;
12708     if (!*str) return 1;
12709     while (*str && isspace(*str)) ++str;
12710     if (!*str) return 1;
12711     return 0;
12712 }
12713
12714 void
12715 DisplayAnalysis()
12716 {
12717     char buf[MSG_SIZ];
12718     char lst[MSG_SIZ / 2];
12719     double nps;
12720     static char *xtra[] = { "", " (--)", " (++)" };
12721     int h, m, s, cs;
12722   
12723     if (programStats.time == 0) {
12724         programStats.time = 1;
12725     }
12726   
12727     if (programStats.got_only_move) {
12728         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12729     } else {
12730         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12731
12732         nps = (u64ToDouble(programStats.nodes) /
12733              ((double)programStats.time /100.0));
12734
12735         cs = programStats.time % 100;
12736         s = programStats.time / 100;
12737         h = (s / (60*60));
12738         s = s - h*60*60;
12739         m = (s/60);
12740         s = s - m*60;
12741
12742         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12743           if (programStats.move_name[0] != NULLCHAR) {
12744             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12745                     programStats.depth,
12746                     programStats.nr_moves-programStats.moves_left,
12747                     programStats.nr_moves, programStats.move_name,
12748                     ((float)programStats.score)/100.0, lst,
12749                     only_one_move(lst)?
12750                     xtra[programStats.got_fail] : "",
12751                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12752           } else {
12753             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12754                     programStats.depth,
12755                     programStats.nr_moves-programStats.moves_left,
12756                     programStats.nr_moves, ((float)programStats.score)/100.0,
12757                     lst,
12758                     only_one_move(lst)?
12759                     xtra[programStats.got_fail] : "",
12760                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12761           }
12762         } else {
12763             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12764                     programStats.depth,
12765                     ((float)programStats.score)/100.0,
12766                     lst,
12767                     only_one_move(lst)?
12768                     xtra[programStats.got_fail] : "",
12769                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12770         }
12771     }
12772     DisplayAnalysisText(buf);
12773 }
12774
12775 void
12776 DisplayComment(moveNumber, text)
12777      int moveNumber;
12778      char *text;
12779 {
12780     char title[MSG_SIZ];
12781     char buf[8000]; // comment can be long!
12782     int score, depth;
12783
12784     if( appData.autoDisplayComment ) {
12785         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12786             strcpy(title, "Comment");
12787         } else {
12788             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12789                     WhiteOnMove(moveNumber) ? " " : ".. ",
12790                     parseList[moveNumber]);
12791         }
12792         // [HGM] PV info: display PV info together with (or as) comment
12793         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12794             if(text == NULL) text = "";                                           
12795             score = pvInfoList[moveNumber].score;
12796             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12797                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12798             text = buf;
12799         }
12800     } else title[0] = 0;
12801
12802     if (text != NULL)
12803         CommentPopUp(title, text);
12804 }
12805
12806 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12807  * might be busy thinking or pondering.  It can be omitted if your
12808  * gnuchess is configured to stop thinking immediately on any user
12809  * input.  However, that gnuchess feature depends on the FIONREAD
12810  * ioctl, which does not work properly on some flavors of Unix.
12811  */
12812 void
12813 Attention(cps)
12814      ChessProgramState *cps;
12815 {
12816 #if ATTENTION
12817     if (!cps->useSigint) return;
12818     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12819     switch (gameMode) {
12820       case MachinePlaysWhite:
12821       case MachinePlaysBlack:
12822       case TwoMachinesPlay:
12823       case IcsPlayingWhite:
12824       case IcsPlayingBlack:
12825       case AnalyzeMode:
12826       case AnalyzeFile:
12827         /* Skip if we know it isn't thinking */
12828         if (!cps->maybeThinking) return;
12829         if (appData.debugMode)
12830           fprintf(debugFP, "Interrupting %s\n", cps->which);
12831         InterruptChildProcess(cps->pr);
12832         cps->maybeThinking = FALSE;
12833         break;
12834       default:
12835         break;
12836     }
12837 #endif /*ATTENTION*/
12838 }
12839
12840 int
12841 CheckFlags()
12842 {
12843     if (whiteTimeRemaining <= 0) {
12844         if (!whiteFlag) {
12845             whiteFlag = TRUE;
12846             if (appData.icsActive) {
12847                 if (appData.autoCallFlag &&
12848                     gameMode == IcsPlayingBlack && !blackFlag) {
12849                   SendToICS(ics_prefix);
12850                   SendToICS("flag\n");
12851                 }
12852             } else {
12853                 if (blackFlag) {
12854                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12855                 } else {
12856                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12857                     if (appData.autoCallFlag) {
12858                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12859                         return TRUE;
12860                     }
12861                 }
12862             }
12863         }
12864     }
12865     if (blackTimeRemaining <= 0) {
12866         if (!blackFlag) {
12867             blackFlag = TRUE;
12868             if (appData.icsActive) {
12869                 if (appData.autoCallFlag &&
12870                     gameMode == IcsPlayingWhite && !whiteFlag) {
12871                   SendToICS(ics_prefix);
12872                   SendToICS("flag\n");
12873                 }
12874             } else {
12875                 if (whiteFlag) {
12876                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12877                 } else {
12878                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12879                     if (appData.autoCallFlag) {
12880                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12881                         return TRUE;
12882                     }
12883                 }
12884             }
12885         }
12886     }
12887     return FALSE;
12888 }
12889
12890 void
12891 CheckTimeControl()
12892 {
12893     if (!appData.clockMode || appData.icsActive ||
12894         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12895
12896     /*
12897      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12898      */
12899     if ( !WhiteOnMove(forwardMostMove) )
12900         /* White made time control */
12901         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12902         /* [HGM] time odds: correct new time quota for time odds! */
12903                                             / WhitePlayer()->timeOdds;
12904       else
12905         /* Black made time control */
12906         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12907                                             / WhitePlayer()->other->timeOdds;
12908 }
12909
12910 void
12911 DisplayBothClocks()
12912 {
12913     int wom = gameMode == EditPosition ?
12914       !blackPlaysFirst : WhiteOnMove(currentMove);
12915     DisplayWhiteClock(whiteTimeRemaining, wom);
12916     DisplayBlackClock(blackTimeRemaining, !wom);
12917 }
12918
12919
12920 /* Timekeeping seems to be a portability nightmare.  I think everyone
12921    has ftime(), but I'm really not sure, so I'm including some ifdefs
12922    to use other calls if you don't.  Clocks will be less accurate if
12923    you have neither ftime nor gettimeofday.
12924 */
12925
12926 /* VS 2008 requires the #include outside of the function */
12927 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12928 #include <sys/timeb.h>
12929 #endif
12930
12931 /* Get the current time as a TimeMark */
12932 void
12933 GetTimeMark(tm)
12934      TimeMark *tm;
12935 {
12936 #if HAVE_GETTIMEOFDAY
12937
12938     struct timeval timeVal;
12939     struct timezone timeZone;
12940
12941     gettimeofday(&timeVal, &timeZone);
12942     tm->sec = (long) timeVal.tv_sec; 
12943     tm->ms = (int) (timeVal.tv_usec / 1000L);
12944
12945 #else /*!HAVE_GETTIMEOFDAY*/
12946 #if HAVE_FTIME
12947
12948 // include <sys/timeb.h> / moved to just above start of function
12949     struct timeb timeB;
12950
12951     ftime(&timeB);
12952     tm->sec = (long) timeB.time;
12953     tm->ms = (int) timeB.millitm;
12954
12955 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12956     tm->sec = (long) time(NULL);
12957     tm->ms = 0;
12958 #endif
12959 #endif
12960 }
12961
12962 /* Return the difference in milliseconds between two
12963    time marks.  We assume the difference will fit in a long!
12964 */
12965 long
12966 SubtractTimeMarks(tm2, tm1)
12967      TimeMark *tm2, *tm1;
12968 {
12969     return 1000L*(tm2->sec - tm1->sec) +
12970            (long) (tm2->ms - tm1->ms);
12971 }
12972
12973
12974 /*
12975  * Code to manage the game clocks.
12976  *
12977  * In tournament play, black starts the clock and then white makes a move.
12978  * We give the human user a slight advantage if he is playing white---the
12979  * clocks don't run until he makes his first move, so it takes zero time.
12980  * Also, we don't account for network lag, so we could get out of sync
12981  * with GNU Chess's clock -- but then, referees are always right.  
12982  */
12983
12984 static TimeMark tickStartTM;
12985 static long intendedTickLength;
12986
12987 long
12988 NextTickLength(timeRemaining)
12989      long timeRemaining;
12990 {
12991     long nominalTickLength, nextTickLength;
12992
12993     if (timeRemaining > 0L && timeRemaining <= 10000L)
12994       nominalTickLength = 100L;
12995     else
12996       nominalTickLength = 1000L;
12997     nextTickLength = timeRemaining % nominalTickLength;
12998     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12999
13000     return nextTickLength;
13001 }
13002
13003 /* Adjust clock one minute up or down */
13004 void
13005 AdjustClock(Boolean which, int dir)
13006 {
13007     if(which) blackTimeRemaining += 60000*dir;
13008     else      whiteTimeRemaining += 60000*dir;
13009     DisplayBothClocks();
13010 }
13011
13012 /* Stop clocks and reset to a fresh time control */
13013 void
13014 ResetClocks() 
13015 {
13016     (void) StopClockTimer();
13017     if (appData.icsActive) {
13018         whiteTimeRemaining = blackTimeRemaining = 0;
13019     } else { /* [HGM] correct new time quote for time odds */
13020         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13021         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13022     }
13023     if (whiteFlag || blackFlag) {
13024         DisplayTitle("");
13025         whiteFlag = blackFlag = FALSE;
13026     }
13027     DisplayBothClocks();
13028 }
13029
13030 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13031
13032 /* Decrement running clock by amount of time that has passed */
13033 void
13034 DecrementClocks()
13035 {
13036     long timeRemaining;
13037     long lastTickLength, fudge;
13038     TimeMark now;
13039
13040     if (!appData.clockMode) return;
13041     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13042         
13043     GetTimeMark(&now);
13044
13045     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13046
13047     /* Fudge if we woke up a little too soon */
13048     fudge = intendedTickLength - lastTickLength;
13049     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13050
13051     if (WhiteOnMove(forwardMostMove)) {
13052         if(whiteNPS >= 0) lastTickLength = 0;
13053         timeRemaining = whiteTimeRemaining -= lastTickLength;
13054         DisplayWhiteClock(whiteTimeRemaining - fudge,
13055                           WhiteOnMove(currentMove));
13056     } else {
13057         if(blackNPS >= 0) lastTickLength = 0;
13058         timeRemaining = blackTimeRemaining -= lastTickLength;
13059         DisplayBlackClock(blackTimeRemaining - fudge,
13060                           !WhiteOnMove(currentMove));
13061     }
13062
13063     if (CheckFlags()) return;
13064         
13065     tickStartTM = now;
13066     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13067     StartClockTimer(intendedTickLength);
13068
13069     /* if the time remaining has fallen below the alarm threshold, sound the
13070      * alarm. if the alarm has sounded and (due to a takeback or time control
13071      * with increment) the time remaining has increased to a level above the
13072      * threshold, reset the alarm so it can sound again. 
13073      */
13074     
13075     if (appData.icsActive && appData.icsAlarm) {
13076
13077         /* make sure we are dealing with the user's clock */
13078         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13079                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13080            )) return;
13081
13082         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13083             alarmSounded = FALSE;
13084         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13085             PlayAlarmSound();
13086             alarmSounded = TRUE;
13087         }
13088     }
13089 }
13090
13091
13092 /* A player has just moved, so stop the previously running
13093    clock and (if in clock mode) start the other one.
13094    We redisplay both clocks in case we're in ICS mode, because
13095    ICS gives us an update to both clocks after every move.
13096    Note that this routine is called *after* forwardMostMove
13097    is updated, so the last fractional tick must be subtracted
13098    from the color that is *not* on move now.
13099 */
13100 void
13101 SwitchClocks()
13102 {
13103     long lastTickLength;
13104     TimeMark now;
13105     int flagged = FALSE;
13106
13107     GetTimeMark(&now);
13108
13109     if (StopClockTimer() && appData.clockMode) {
13110         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13111         if (WhiteOnMove(forwardMostMove)) {
13112             if(blackNPS >= 0) lastTickLength = 0;
13113             blackTimeRemaining -= lastTickLength;
13114            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13115 //         if(pvInfoList[forwardMostMove-1].time == -1)
13116                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13117                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13118         } else {
13119            if(whiteNPS >= 0) lastTickLength = 0;
13120            whiteTimeRemaining -= lastTickLength;
13121            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13122 //         if(pvInfoList[forwardMostMove-1].time == -1)
13123                  pvInfoList[forwardMostMove-1].time = 
13124                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13125         }
13126         flagged = CheckFlags();
13127     }
13128     CheckTimeControl();
13129
13130     if (flagged || !appData.clockMode) return;
13131
13132     switch (gameMode) {
13133       case MachinePlaysBlack:
13134       case MachinePlaysWhite:
13135       case BeginningOfGame:
13136         if (pausing) return;
13137         break;
13138
13139       case EditGame:
13140       case PlayFromGameFile:
13141       case IcsExamining:
13142         return;
13143
13144       default:
13145         break;
13146     }
13147
13148     tickStartTM = now;
13149     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13150       whiteTimeRemaining : blackTimeRemaining);
13151     StartClockTimer(intendedTickLength);
13152 }
13153         
13154
13155 /* Stop both clocks */
13156 void
13157 StopClocks()
13158 {       
13159     long lastTickLength;
13160     TimeMark now;
13161
13162     if (!StopClockTimer()) return;
13163     if (!appData.clockMode) return;
13164
13165     GetTimeMark(&now);
13166
13167     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13168     if (WhiteOnMove(forwardMostMove)) {
13169         if(whiteNPS >= 0) lastTickLength = 0;
13170         whiteTimeRemaining -= lastTickLength;
13171         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13172     } else {
13173         if(blackNPS >= 0) lastTickLength = 0;
13174         blackTimeRemaining -= lastTickLength;
13175         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13176     }
13177     CheckFlags();
13178 }
13179         
13180 /* Start clock of player on move.  Time may have been reset, so
13181    if clock is already running, stop and restart it. */
13182 void
13183 StartClocks()
13184 {
13185     (void) StopClockTimer(); /* in case it was running already */
13186     DisplayBothClocks();
13187     if (CheckFlags()) return;
13188
13189     if (!appData.clockMode) return;
13190     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13191
13192     GetTimeMark(&tickStartTM);
13193     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13194       whiteTimeRemaining : blackTimeRemaining);
13195
13196    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13197     whiteNPS = blackNPS = -1; 
13198     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13199        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13200         whiteNPS = first.nps;
13201     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13202        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13203         blackNPS = first.nps;
13204     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13205         whiteNPS = second.nps;
13206     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13207         blackNPS = second.nps;
13208     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13209
13210     StartClockTimer(intendedTickLength);
13211 }
13212
13213 char *
13214 TimeString(ms)
13215      long ms;
13216 {
13217     long second, minute, hour, day;
13218     char *sign = "";
13219     static char buf[32];
13220     
13221     if (ms > 0 && ms <= 9900) {
13222       /* convert milliseconds to tenths, rounding up */
13223       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13224
13225       sprintf(buf, " %03.1f ", tenths/10.0);
13226       return buf;
13227     }
13228
13229     /* convert milliseconds to seconds, rounding up */
13230     /* use floating point to avoid strangeness of integer division
13231        with negative dividends on many machines */
13232     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13233
13234     if (second < 0) {
13235         sign = "-";
13236         second = -second;
13237     }
13238     
13239     day = second / (60 * 60 * 24);
13240     second = second % (60 * 60 * 24);
13241     hour = second / (60 * 60);
13242     second = second % (60 * 60);
13243     minute = second / 60;
13244     second = second % 60;
13245     
13246     if (day > 0)
13247       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13248               sign, day, hour, minute, second);
13249     else if (hour > 0)
13250       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13251     else
13252       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13253     
13254     return buf;
13255 }
13256
13257
13258 /*
13259  * This is necessary because some C libraries aren't ANSI C compliant yet.
13260  */
13261 char *
13262 StrStr(string, match)
13263      char *string, *match;
13264 {
13265     int i, length;
13266     
13267     length = strlen(match);
13268     
13269     for (i = strlen(string) - length; i >= 0; i--, string++)
13270       if (!strncmp(match, string, length))
13271         return string;
13272     
13273     return NULL;
13274 }
13275
13276 char *
13277 StrCaseStr(string, match)
13278      char *string, *match;
13279 {
13280     int i, j, length;
13281     
13282     length = strlen(match);
13283     
13284     for (i = strlen(string) - length; i >= 0; i--, string++) {
13285         for (j = 0; j < length; j++) {
13286             if (ToLower(match[j]) != ToLower(string[j]))
13287               break;
13288         }
13289         if (j == length) return string;
13290     }
13291
13292     return NULL;
13293 }
13294
13295 #ifndef _amigados
13296 int
13297 StrCaseCmp(s1, s2)
13298      char *s1, *s2;
13299 {
13300     char c1, c2;
13301     
13302     for (;;) {
13303         c1 = ToLower(*s1++);
13304         c2 = ToLower(*s2++);
13305         if (c1 > c2) return 1;
13306         if (c1 < c2) return -1;
13307         if (c1 == NULLCHAR) return 0;
13308     }
13309 }
13310
13311
13312 int
13313 ToLower(c)
13314      int c;
13315 {
13316     return isupper(c) ? tolower(c) : c;
13317 }
13318
13319
13320 int
13321 ToUpper(c)
13322      int c;
13323 {
13324     return islower(c) ? toupper(c) : c;
13325 }
13326 #endif /* !_amigados    */
13327
13328 char *
13329 StrSave(s)
13330      char *s;
13331 {
13332     char *ret;
13333
13334     if ((ret = (char *) malloc(strlen(s) + 1))) {
13335         strcpy(ret, s);
13336     }
13337     return ret;
13338 }
13339
13340 char *
13341 StrSavePtr(s, savePtr)
13342      char *s, **savePtr;
13343 {
13344     if (*savePtr) {
13345         free(*savePtr);
13346     }
13347     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13348         strcpy(*savePtr, s);
13349     }
13350     return(*savePtr);
13351 }
13352
13353 char *
13354 PGNDate()
13355 {
13356     time_t clock;
13357     struct tm *tm;
13358     char buf[MSG_SIZ];
13359
13360     clock = time((time_t *)NULL);
13361     tm = localtime(&clock);
13362     sprintf(buf, "%04d.%02d.%02d",
13363             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13364     return StrSave(buf);
13365 }
13366
13367
13368 char *
13369 PositionToFEN(move, overrideCastling)
13370      int move;
13371      char *overrideCastling;
13372 {
13373     int i, j, fromX, fromY, toX, toY;
13374     int whiteToPlay;
13375     char buf[128];
13376     char *p, *q;
13377     int emptycount;
13378     ChessSquare piece;
13379
13380     whiteToPlay = (gameMode == EditPosition) ?
13381       !blackPlaysFirst : (move % 2 == 0);
13382     p = buf;
13383
13384     /* Piece placement data */
13385     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13386         emptycount = 0;
13387         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13388             if (boards[move][i][j] == EmptySquare) {
13389                 emptycount++;
13390             } else { ChessSquare piece = boards[move][i][j];
13391                 if (emptycount > 0) {
13392                     if(emptycount<10) /* [HGM] can be >= 10 */
13393                         *p++ = '0' + emptycount;
13394                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13395                     emptycount = 0;
13396                 }
13397                 if(PieceToChar(piece) == '+') {
13398                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13399                     *p++ = '+';
13400                     piece = (ChessSquare)(DEMOTED piece);
13401                 } 
13402                 *p++ = PieceToChar(piece);
13403                 if(p[-1] == '~') {
13404                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13405                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13406                     *p++ = '~';
13407                 }
13408             }
13409         }
13410         if (emptycount > 0) {
13411             if(emptycount<10) /* [HGM] can be >= 10 */
13412                 *p++ = '0' + emptycount;
13413             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13414             emptycount = 0;
13415         }
13416         *p++ = '/';
13417     }
13418     *(p - 1) = ' ';
13419
13420     /* [HGM] print Crazyhouse or Shogi holdings */
13421     if( gameInfo.holdingsWidth ) {
13422         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13423         q = p;
13424         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13425             piece = boards[move][i][BOARD_WIDTH-1];
13426             if( piece != EmptySquare )
13427               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13428                   *p++ = PieceToChar(piece);
13429         }
13430         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13431             piece = boards[move][BOARD_HEIGHT-i-1][0];
13432             if( piece != EmptySquare )
13433               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13434                   *p++ = PieceToChar(piece);
13435         }
13436
13437         if( q == p ) *p++ = '-';
13438         *p++ = ']';
13439         *p++ = ' ';
13440     }
13441
13442     /* Active color */
13443     *p++ = whiteToPlay ? 'w' : 'b';
13444     *p++ = ' ';
13445
13446   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13447     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13448   } else {
13449   if(nrCastlingRights) {
13450      q = p;
13451      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13452        /* [HGM] write directly from rights */
13453            if(castlingRights[move][2] >= 0 &&
13454               castlingRights[move][0] >= 0   )
13455                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13456            if(castlingRights[move][2] >= 0 &&
13457               castlingRights[move][1] >= 0   )
13458                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13459            if(castlingRights[move][5] >= 0 &&
13460               castlingRights[move][3] >= 0   )
13461                 *p++ = castlingRights[move][3] + AAA;
13462            if(castlingRights[move][5] >= 0 &&
13463               castlingRights[move][4] >= 0   )
13464                 *p++ = castlingRights[move][4] + AAA;
13465      } else {
13466
13467         /* [HGM] write true castling rights */
13468         if( nrCastlingRights == 6 ) {
13469             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13470                castlingRights[move][2] >= 0  ) *p++ = 'K';
13471             if(castlingRights[move][1] == BOARD_LEFT &&
13472                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13473             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13474                castlingRights[move][5] >= 0  ) *p++ = 'k';
13475             if(castlingRights[move][4] == BOARD_LEFT &&
13476                castlingRights[move][5] >= 0  ) *p++ = 'q';
13477         }
13478      }
13479      if (q == p) *p++ = '-'; /* No castling rights */
13480      *p++ = ' ';
13481   }
13482
13483   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13484      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13485     /* En passant target square */
13486     if (move > backwardMostMove) {
13487         fromX = moveList[move - 1][0] - AAA;
13488         fromY = moveList[move - 1][1] - ONE;
13489         toX = moveList[move - 1][2] - AAA;
13490         toY = moveList[move - 1][3] - ONE;
13491         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13492             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13493             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13494             fromX == toX) {
13495             /* 2-square pawn move just happened */
13496             *p++ = toX + AAA;
13497             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13498         } else {
13499             *p++ = '-';
13500         }
13501     } else {
13502         *p++ = '-';
13503     }
13504     *p++ = ' ';
13505   }
13506   }
13507
13508     /* [HGM] find reversible plies */
13509     {   int i = 0, j=move;
13510
13511         if (appData.debugMode) { int k;
13512             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13513             for(k=backwardMostMove; k<=forwardMostMove; k++)
13514                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13515
13516         }
13517
13518         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13519         if( j == backwardMostMove ) i += initialRulePlies;
13520         sprintf(p, "%d ", i);
13521         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13522     }
13523     /* Fullmove number */
13524     sprintf(p, "%d", (move / 2) + 1);
13525     
13526     return StrSave(buf);
13527 }
13528
13529 Boolean
13530 ParseFEN(board, blackPlaysFirst, fen)
13531     Board board;
13532      int *blackPlaysFirst;
13533      char *fen;
13534 {
13535     int i, j;
13536     char *p;
13537     int emptycount;
13538     ChessSquare piece;
13539
13540     p = fen;
13541
13542     /* [HGM] by default clear Crazyhouse holdings, if present */
13543     if(gameInfo.holdingsWidth) {
13544        for(i=0; i<BOARD_HEIGHT; i++) {
13545            board[i][0]             = EmptySquare; /* black holdings */
13546            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13547            board[i][1]             = (ChessSquare) 0; /* black counts */
13548            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13549        }
13550     }
13551
13552     /* Piece placement data */
13553     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13554         j = 0;
13555         for (;;) {
13556             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13557                 if (*p == '/') p++;
13558                 emptycount = gameInfo.boardWidth - j;
13559                 while (emptycount--)
13560                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13561                 break;
13562 #if(BOARD_SIZE >= 10)
13563             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13564                 p++; emptycount=10;
13565                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13566                 while (emptycount--)
13567                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13568 #endif
13569             } else if (isdigit(*p)) {
13570                 emptycount = *p++ - '0';
13571                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13572                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13573                 while (emptycount--)
13574                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13575             } else if (*p == '+' || isalpha(*p)) {
13576                 if (j >= gameInfo.boardWidth) return FALSE;
13577                 if(*p=='+') {
13578                     piece = CharToPiece(*++p);
13579                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13580                     piece = (ChessSquare) (PROMOTED piece ); p++;
13581                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13582                 } else piece = CharToPiece(*p++);
13583
13584                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13585                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13586                     piece = (ChessSquare) (PROMOTED piece);
13587                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13588                     p++;
13589                 }
13590                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13591             } else {
13592                 return FALSE;
13593             }
13594         }
13595     }
13596     while (*p == '/' || *p == ' ') p++;
13597
13598     /* [HGM] look for Crazyhouse holdings here */
13599     while(*p==' ') p++;
13600     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13601         if(*p == '[') p++;
13602         if(*p == '-' ) *p++; /* empty holdings */ else {
13603             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13604             /* if we would allow FEN reading to set board size, we would   */
13605             /* have to add holdings and shift the board read so far here   */
13606             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13607                 *p++;
13608                 if((int) piece >= (int) BlackPawn ) {
13609                     i = (int)piece - (int)BlackPawn;
13610                     i = PieceToNumber((ChessSquare)i);
13611                     if( i >= gameInfo.holdingsSize ) return FALSE;
13612                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13613                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13614                 } else {
13615                     i = (int)piece - (int)WhitePawn;
13616                     i = PieceToNumber((ChessSquare)i);
13617                     if( i >= gameInfo.holdingsSize ) return FALSE;
13618                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13619                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13620                 }
13621             }
13622         }
13623         if(*p == ']') *p++;
13624     }
13625
13626     while(*p == ' ') p++;
13627
13628     /* Active color */
13629     switch (*p++) {
13630       case 'w':
13631         *blackPlaysFirst = FALSE;
13632         break;
13633       case 'b': 
13634         *blackPlaysFirst = TRUE;
13635         break;
13636       default:
13637         return FALSE;
13638     }
13639
13640     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13641     /* return the extra info in global variiables             */
13642
13643     /* set defaults in case FEN is incomplete */
13644     FENepStatus = EP_UNKNOWN;
13645     for(i=0; i<nrCastlingRights; i++ ) {
13646         FENcastlingRights[i] =
13647             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13648     }   /* assume possible unless obviously impossible */
13649     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13650     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13651     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13652     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13653     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13654     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13655     FENrulePlies = 0;
13656
13657     while(*p==' ') p++;
13658     if(nrCastlingRights) {
13659       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13660           /* castling indicator present, so default becomes no castlings */
13661           for(i=0; i<nrCastlingRights; i++ ) {
13662                  FENcastlingRights[i] = -1;
13663           }
13664       }
13665       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13666              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13667              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13668              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13669         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13670
13671         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13672             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13673             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13674         }
13675         switch(c) {
13676           case'K':
13677               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13678               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13679               FENcastlingRights[2] = whiteKingFile;
13680               break;
13681           case'Q':
13682               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13683               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13684               FENcastlingRights[2] = whiteKingFile;
13685               break;
13686           case'k':
13687               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13688               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13689               FENcastlingRights[5] = blackKingFile;
13690               break;
13691           case'q':
13692               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13693               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13694               FENcastlingRights[5] = blackKingFile;
13695           case '-':
13696               break;
13697           default: /* FRC castlings */
13698               if(c >= 'a') { /* black rights */
13699                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13700                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13701                   if(i == BOARD_RGHT) break;
13702                   FENcastlingRights[5] = i;
13703                   c -= AAA;
13704                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13705                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13706                   if(c > i)
13707                       FENcastlingRights[3] = c;
13708                   else
13709                       FENcastlingRights[4] = c;
13710               } else { /* white rights */
13711                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13712                     if(board[0][i] == WhiteKing) break;
13713                   if(i == BOARD_RGHT) break;
13714                   FENcastlingRights[2] = i;
13715                   c -= AAA - 'a' + 'A';
13716                   if(board[0][c] >= WhiteKing) break;
13717                   if(c > i)
13718                       FENcastlingRights[0] = c;
13719                   else
13720                       FENcastlingRights[1] = c;
13721               }
13722         }
13723       }
13724     if (appData.debugMode) {
13725         fprintf(debugFP, "FEN castling rights:");
13726         for(i=0; i<nrCastlingRights; i++)
13727         fprintf(debugFP, " %d", FENcastlingRights[i]);
13728         fprintf(debugFP, "\n");
13729     }
13730
13731       while(*p==' ') p++;
13732     }
13733
13734     /* read e.p. field in games that know e.p. capture */
13735     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13736        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13737       if(*p=='-') {
13738         p++; FENepStatus = EP_NONE;
13739       } else {
13740          char c = *p++ - AAA;
13741
13742          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13743          if(*p >= '0' && *p <='9') *p++;
13744          FENepStatus = c;
13745       }
13746     }
13747
13748
13749     if(sscanf(p, "%d", &i) == 1) {
13750         FENrulePlies = i; /* 50-move ply counter */
13751         /* (The move number is still ignored)    */
13752     }
13753
13754     return TRUE;
13755 }
13756       
13757 void
13758 EditPositionPasteFEN(char *fen)
13759 {
13760   if (fen != NULL) {
13761     Board initial_position;
13762
13763     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13764       DisplayError(_("Bad FEN position in clipboard"), 0);
13765       return ;
13766     } else {
13767       int savedBlackPlaysFirst = blackPlaysFirst;
13768       EditPositionEvent();
13769       blackPlaysFirst = savedBlackPlaysFirst;
13770       CopyBoard(boards[0], initial_position);
13771           /* [HGM] copy FEN attributes as well */
13772           {   int i;
13773               initialRulePlies = FENrulePlies;
13774               epStatus[0] = FENepStatus;
13775               for( i=0; i<nrCastlingRights; i++ )
13776                   castlingRights[0][i] = FENcastlingRights[i];
13777           }
13778       EditPositionDone();
13779       DisplayBothClocks();
13780       DrawPosition(FALSE, boards[currentMove]);
13781     }
13782   }
13783 }