Added a command-line option -keepLineBreaksICS true/false to control line joining.
[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     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1553       if (StrCaseStr(e, variantNames[i])) {
1554         v = (VariantClass) i;
1555         found = TRUE;
1556         break;
1557       }
1558     }
1559
1560     if (!found) {
1561       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1562           || StrCaseStr(e, "wild/fr") 
1563           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1564         v = VariantFischeRandom;
1565       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1566                  (i = 1, p = StrCaseStr(e, "w"))) {
1567         p += i;
1568         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1569         if (isdigit(*p)) {
1570           wnum = atoi(p);
1571         } else {
1572           wnum = -1;
1573         }
1574         switch (wnum) {
1575         case 0: /* FICS only, actually */
1576         case 1:
1577           /* Castling legal even if K starts on d-file */
1578           v = VariantWildCastle;
1579           break;
1580         case 2:
1581         case 3:
1582         case 4:
1583           /* Castling illegal even if K & R happen to start in
1584              normal positions. */
1585           v = VariantNoCastle;
1586           break;
1587         case 5:
1588         case 7:
1589         case 8:
1590         case 10:
1591         case 11:
1592         case 12:
1593         case 13:
1594         case 14:
1595         case 15:
1596         case 18:
1597         case 19:
1598           /* Castling legal iff K & R start in normal positions */
1599           v = VariantNormal;
1600           break;
1601         case 6:
1602         case 20:
1603         case 21:
1604           /* Special wilds for position setup; unclear what to do here */
1605           v = VariantLoadable;
1606           break;
1607         case 9:
1608           /* Bizarre ICC game */
1609           v = VariantTwoKings;
1610           break;
1611         case 16:
1612           v = VariantKriegspiel;
1613           break;
1614         case 17:
1615           v = VariantLosers;
1616           break;
1617         case 22:
1618           v = VariantFischeRandom;
1619           break;
1620         case 23:
1621           v = VariantCrazyhouse;
1622           break;
1623         case 24:
1624           v = VariantBughouse;
1625           break;
1626         case 25:
1627           v = Variant3Check;
1628           break;
1629         case 26:
1630           /* Not quite the same as FICS suicide! */
1631           v = VariantGiveaway;
1632           break;
1633         case 27:
1634           v = VariantAtomic;
1635           break;
1636         case 28:
1637           v = VariantShatranj;
1638           break;
1639
1640         /* Temporary names for future ICC types.  The name *will* change in 
1641            the next xboard/WinBoard release after ICC defines it. */
1642         case 29:
1643           v = Variant29;
1644           break;
1645         case 30:
1646           v = Variant30;
1647           break;
1648         case 31:
1649           v = Variant31;
1650           break;
1651         case 32:
1652           v = Variant32;
1653           break;
1654         case 33:
1655           v = Variant33;
1656           break;
1657         case 34:
1658           v = Variant34;
1659           break;
1660         case 35:
1661           v = Variant35;
1662           break;
1663         case 36:
1664           v = Variant36;
1665           break;
1666         case 37:
1667           v = VariantShogi;
1668           break;
1669         case 38:
1670           v = VariantXiangqi;
1671           break;
1672         case 39:
1673           v = VariantCourier;
1674           break;
1675         case 40:
1676           v = VariantGothic;
1677           break;
1678         case 41:
1679           v = VariantCapablanca;
1680           break;
1681         case 42:
1682           v = VariantKnightmate;
1683           break;
1684         case 43:
1685           v = VariantFairy;
1686           break;
1687         case 44:
1688           v = VariantCylinder;
1689           break;
1690         case 45:
1691           v = VariantFalcon;
1692           break;
1693         case 46:
1694           v = VariantCapaRandom;
1695           break;
1696         case 47:
1697           v = VariantBerolina;
1698           break;
1699         case 48:
1700           v = VariantJanus;
1701           break;
1702         case 49:
1703           v = VariantSuper;
1704           break;
1705         case 50:
1706           v = VariantGreat;
1707           break;
1708         case -1:
1709           /* Found "wild" or "w" in the string but no number;
1710              must assume it's normal chess. */
1711           v = VariantNormal;
1712           break;
1713         default:
1714           sprintf(buf, _("Unknown wild type %d"), wnum);
1715           DisplayError(buf, 0);
1716           v = VariantUnknown;
1717           break;
1718         }
1719       }
1720     }
1721     if (appData.debugMode) {
1722       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1723               e, wnum, VariantName(v));
1724     }
1725     return v;
1726 }
1727
1728 static int leftover_start = 0, leftover_len = 0;
1729 char star_match[STAR_MATCH_N][MSG_SIZ];
1730
1731 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1732    advance *index beyond it, and set leftover_start to the new value of
1733    *index; else return FALSE.  If pattern contains the character '*', it
1734    matches any sequence of characters not containing '\r', '\n', or the
1735    character following the '*' (if any), and the matched sequence(s) are
1736    copied into star_match.
1737    */
1738 int
1739 looking_at(buf, index, pattern)
1740      char *buf;
1741      int *index;
1742      char *pattern;
1743 {
1744     char *bufp = &buf[*index], *patternp = pattern;
1745     int star_count = 0;
1746     char *matchp = star_match[0];
1747     
1748     for (;;) {
1749         if (*patternp == NULLCHAR) {
1750             *index = leftover_start = bufp - buf;
1751             *matchp = NULLCHAR;
1752             return TRUE;
1753         }
1754         if (*bufp == NULLCHAR) return FALSE;
1755         if (*patternp == '*') {
1756             if (*bufp == *(patternp + 1)) {
1757                 *matchp = NULLCHAR;
1758                 matchp = star_match[++star_count];
1759                 patternp += 2;
1760                 bufp++;
1761                 continue;
1762             } else if (*bufp == '\n' || *bufp == '\r') {
1763                 patternp++;
1764                 if (*patternp == NULLCHAR)
1765                   continue;
1766                 else
1767                   return FALSE;
1768             } else {
1769                 *matchp++ = *bufp++;
1770                 continue;
1771             }
1772         }
1773         if (*patternp != *bufp) return FALSE;
1774         patternp++;
1775         bufp++;
1776     }
1777 }
1778
1779 void
1780 SendToPlayer(data, length)
1781      char *data;
1782      int length;
1783 {
1784     int error, outCount;
1785     outCount = OutputToProcess(NoProc, data, length, &error);
1786     if (outCount < length) {
1787         DisplayFatalError(_("Error writing to display"), error, 1);
1788     }
1789 }
1790
1791 void
1792 PackHolding(packed, holding)
1793      char packed[];
1794      char *holding;
1795 {
1796     char *p = holding;
1797     char *q = packed;
1798     int runlength = 0;
1799     int curr = 9999;
1800     do {
1801         if (*p == curr) {
1802             runlength++;
1803         } else {
1804             switch (runlength) {
1805               case 0:
1806                 break;
1807               case 1:
1808                 *q++ = curr;
1809                 break;
1810               case 2:
1811                 *q++ = curr;
1812                 *q++ = curr;
1813                 break;
1814               default:
1815                 sprintf(q, "%d", runlength);
1816                 while (*q) q++;
1817                 *q++ = curr;
1818                 break;
1819             }
1820             runlength = 1;
1821             curr = *p;
1822         }
1823     } while (*p++);
1824     *q = NULLCHAR;
1825 }
1826
1827 /* Telnet protocol requests from the front end */
1828 void
1829 TelnetRequest(ddww, option)
1830      unsigned char ddww, option;
1831 {
1832     unsigned char msg[3];
1833     int outCount, outError;
1834
1835     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1836
1837     if (appData.debugMode) {
1838         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1839         switch (ddww) {
1840           case TN_DO:
1841             ddwwStr = "DO";
1842             break;
1843           case TN_DONT:
1844             ddwwStr = "DONT";
1845             break;
1846           case TN_WILL:
1847             ddwwStr = "WILL";
1848             break;
1849           case TN_WONT:
1850             ddwwStr = "WONT";
1851             break;
1852           default:
1853             ddwwStr = buf1;
1854             sprintf(buf1, "%d", ddww);
1855             break;
1856         }
1857         switch (option) {
1858           case TN_ECHO:
1859             optionStr = "ECHO";
1860             break;
1861           default:
1862             optionStr = buf2;
1863             sprintf(buf2, "%d", option);
1864             break;
1865         }
1866         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1867     }
1868     msg[0] = TN_IAC;
1869     msg[1] = ddww;
1870     msg[2] = option;
1871     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1872     if (outCount < 3) {
1873         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1874     }
1875 }
1876
1877 void
1878 DoEcho()
1879 {
1880     if (!appData.icsActive) return;
1881     TelnetRequest(TN_DO, TN_ECHO);
1882 }
1883
1884 void
1885 DontEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DONT, TN_ECHO);
1889 }
1890
1891 void
1892 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1893 {
1894     /* put the holdings sent to us by the server on the board holdings area */
1895     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1896     char p;
1897     ChessSquare piece;
1898
1899     if(gameInfo.holdingsWidth < 2)  return;
1900
1901     if( (int)lowestPiece >= BlackPawn ) {
1902         holdingsColumn = 0;
1903         countsColumn = 1;
1904         holdingsStartRow = BOARD_HEIGHT-1;
1905         direction = -1;
1906     } else {
1907         holdingsColumn = BOARD_WIDTH-1;
1908         countsColumn = BOARD_WIDTH-2;
1909         holdingsStartRow = 0;
1910         direction = 1;
1911     }
1912
1913     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1914         board[i][holdingsColumn] = EmptySquare;
1915         board[i][countsColumn]   = (ChessSquare) 0;
1916     }
1917     while( (p=*holdings++) != NULLCHAR ) {
1918         piece = CharToPiece( ToUpper(p) );
1919         if(piece == EmptySquare) continue;
1920         /*j = (int) piece - (int) WhitePawn;*/
1921         j = PieceToNumber(piece);
1922         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1923         if(j < 0) continue;               /* should not happen */
1924         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1925         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1926         board[holdingsStartRow+j*direction][countsColumn]++;
1927     }
1928
1929 }
1930
1931
1932 void
1933 VariantSwitch(Board board, VariantClass newVariant)
1934 {
1935    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1936    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1937 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1938
1939    startedFromPositionFile = FALSE;
1940    if(gameInfo.variant == newVariant) return;
1941
1942    /* [HGM] This routine is called each time an assignment is made to
1943     * gameInfo.variant during a game, to make sure the board sizes
1944     * are set to match the new variant. If that means adding or deleting
1945     * holdings, we shift the playing board accordingly
1946     * This kludge is needed because in ICS observe mode, we get boards
1947     * of an ongoing game without knowing the variant, and learn about the
1948     * latter only later. This can be because of the move list we requested,
1949     * in which case the game history is refilled from the beginning anyway,
1950     * but also when receiving holdings of a crazyhouse game. In the latter
1951     * case we want to add those holdings to the already received position.
1952     */
1953
1954
1955   if (appData.debugMode) {
1956     fprintf(debugFP, "Switch board from %s to %s\n",
1957                VariantName(gameInfo.variant), VariantName(newVariant));
1958     setbuf(debugFP, NULL);
1959   }
1960     shuffleOpenings = 0;       /* [HGM] shuffle */
1961     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1962     switch(newVariant) {
1963             case VariantShogi:
1964               newWidth = 9;  newHeight = 9;
1965               gameInfo.holdingsSize = 7;
1966             case VariantBughouse:
1967             case VariantCrazyhouse:
1968               newHoldingsWidth = 2; break;
1969             default:
1970               newHoldingsWidth = gameInfo.holdingsSize = 0;
1971     }
1972
1973     if(newWidth  != gameInfo.boardWidth  ||
1974        newHeight != gameInfo.boardHeight ||
1975        newHoldingsWidth != gameInfo.holdingsWidth ) {
1976
1977         /* shift position to new playing area, if needed */
1978         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1979            for(i=0; i<BOARD_HEIGHT; i++) 
1980                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1981                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1982                                                      board[i][j];
1983            for(i=0; i<newHeight; i++) {
1984                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1985                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1986            }
1987         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1988            for(i=0; i<BOARD_HEIGHT; i++)
1989                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1990                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1991                                                  board[i][j];
1992         }
1993
1994         gameInfo.boardWidth  = newWidth;
1995         gameInfo.boardHeight = newHeight;
1996         gameInfo.holdingsWidth = newHoldingsWidth;
1997         gameInfo.variant = newVariant;
1998         InitDrawingSizes(-2, 0);
1999
2000         /* [HGM] The following should definitely be solved in a better way */
2001         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2002     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2003
2004     forwardMostMove = oldForwardMostMove;
2005     backwardMostMove = oldBackwardMostMove;
2006     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2007 }
2008
2009 static int loggedOn = FALSE;
2010
2011 /*-- Game start info cache: --*/
2012 int gs_gamenum;
2013 char gs_kind[MSG_SIZ];
2014 static char player1Name[128] = "";
2015 static char player2Name[128] = "";
2016 static int player1Rating = -1;
2017 static int player2Rating = -1;
2018 /*----------------------------*/
2019
2020 ColorClass curColor = ColorNormal;
2021 int suppressKibitz = 0;
2022
2023 void
2024 read_from_ics(isr, closure, data, count, error)
2025      InputSourceRef isr;
2026      VOIDSTAR closure;
2027      char *data;
2028      int count;
2029      int error;
2030 {
2031 #define BUF_SIZE 8192
2032 #define STARTED_NONE 0
2033 #define STARTED_MOVES 1
2034 #define STARTED_BOARD 2
2035 #define STARTED_OBSERVE 3
2036 #define STARTED_HOLDINGS 4
2037 #define STARTED_CHATTER 5
2038 #define STARTED_COMMENT 6
2039 #define STARTED_MOVES_NOHIDE 7
2040     
2041     static int started = STARTED_NONE;
2042     static char parse[20000];
2043     static int parse_pos = 0;
2044     static char buf[BUF_SIZE + 1];
2045     static int firstTime = TRUE, intfSet = FALSE;
2046     static ColorClass prevColor = ColorNormal;
2047     static int savingComment = FALSE;
2048     char str[500];
2049     int i, oldi;
2050     int buf_len;
2051     int next_out;
2052     int tkind;
2053     int backup;    /* [DM] For zippy color lines */
2054     char *p;
2055     char talker[MSG_SIZ]; // [HGM] chat
2056     int channel;
2057
2058     if (appData.debugMode) {
2059       if (!error) {
2060         fprintf(debugFP, "<ICS: ");
2061         show_bytes(debugFP, data, count);
2062         fprintf(debugFP, "\n");
2063       }
2064     }
2065
2066     if (appData.debugMode) { int f = forwardMostMove;
2067         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2068                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2069     }
2070     if (count > 0) {
2071         /* If last read ended with a partial line that we couldn't parse,
2072            prepend it to the new read and try again. */
2073         if (leftover_len > 0) {
2074             for (i=0; i<leftover_len; i++)
2075               buf[i] = buf[leftover_start + i];
2076         }
2077
2078         /* Copy in new characters, removing nulls and \r's */
2079         buf_len = leftover_len;
2080         for (i = 0; i < count; i++) {
2081             if (data[i] != NULLCHAR && data[i] != '\r')
2082               buf[buf_len++] = data[i];
2083             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2084                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2085                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2086                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2087                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2088             }
2089         }
2090
2091         buf[buf_len] = NULLCHAR;
2092         next_out = leftover_len;
2093         leftover_start = 0;
2094         
2095         i = 0;
2096         while (i < buf_len) {
2097             /* Deal with part of the TELNET option negotiation
2098                protocol.  We refuse to do anything beyond the
2099                defaults, except that we allow the WILL ECHO option,
2100                which ICS uses to turn off password echoing when we are
2101                directly connected to it.  We reject this option
2102                if localLineEditing mode is on (always on in xboard)
2103                and we are talking to port 23, which might be a real
2104                telnet server that will try to keep WILL ECHO on permanently.
2105              */
2106             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2107                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2108                 unsigned char option;
2109                 oldi = i;
2110                 switch ((unsigned char) buf[++i]) {
2111                   case TN_WILL:
2112                     if (appData.debugMode)
2113                       fprintf(debugFP, "\n<WILL ");
2114                     switch (option = (unsigned char) buf[++i]) {
2115                       case TN_ECHO:
2116                         if (appData.debugMode)
2117                           fprintf(debugFP, "ECHO ");
2118                         /* Reply only if this is a change, according
2119                            to the protocol rules. */
2120                         if (remoteEchoOption) break;
2121                         if (appData.localLineEditing &&
2122                             atoi(appData.icsPort) == TN_PORT) {
2123                             TelnetRequest(TN_DONT, TN_ECHO);
2124                         } else {
2125                             EchoOff();
2126                             TelnetRequest(TN_DO, TN_ECHO);
2127                             remoteEchoOption = TRUE;
2128                         }
2129                         break;
2130                       default:
2131                         if (appData.debugMode)
2132                           fprintf(debugFP, "%d ", option);
2133                         /* Whatever this is, we don't want it. */
2134                         TelnetRequest(TN_DONT, option);
2135                         break;
2136                     }
2137                     break;
2138                   case TN_WONT:
2139                     if (appData.debugMode)
2140                       fprintf(debugFP, "\n<WONT ");
2141                     switch (option = (unsigned char) buf[++i]) {
2142                       case TN_ECHO:
2143                         if (appData.debugMode)
2144                           fprintf(debugFP, "ECHO ");
2145                         /* Reply only if this is a change, according
2146                            to the protocol rules. */
2147                         if (!remoteEchoOption) break;
2148                         EchoOn();
2149                         TelnetRequest(TN_DONT, TN_ECHO);
2150                         remoteEchoOption = FALSE;
2151                         break;
2152                       default:
2153                         if (appData.debugMode)
2154                           fprintf(debugFP, "%d ", (unsigned char) option);
2155                         /* Whatever this is, it must already be turned
2156                            off, because we never agree to turn on
2157                            anything non-default, so according to the
2158                            protocol rules, we don't reply. */
2159                         break;
2160                     }
2161                     break;
2162                   case TN_DO:
2163                     if (appData.debugMode)
2164                       fprintf(debugFP, "\n<DO ");
2165                     switch (option = (unsigned char) buf[++i]) {
2166                       default:
2167                         /* Whatever this is, we refuse to do it. */
2168                         if (appData.debugMode)
2169                           fprintf(debugFP, "%d ", option);
2170                         TelnetRequest(TN_WONT, option);
2171                         break;
2172                     }
2173                     break;
2174                   case TN_DONT:
2175                     if (appData.debugMode)
2176                       fprintf(debugFP, "\n<DONT ");
2177                     switch (option = (unsigned char) buf[++i]) {
2178                       default:
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "%d ", option);
2181                         /* Whatever this is, we are already not doing
2182                            it, because we never agree to do anything
2183                            non-default, so according to the protocol
2184                            rules, we don't reply. */
2185                         break;
2186                     }
2187                     break;
2188                   case TN_IAC:
2189                     if (appData.debugMode)
2190                       fprintf(debugFP, "\n<IAC ");
2191                     /* Doubled IAC; pass it through */
2192                     i--;
2193                     break;
2194                   default:
2195                     if (appData.debugMode)
2196                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2197                     /* Drop all other telnet commands on the floor */
2198                     break;
2199                 }
2200                 if (oldi > next_out)
2201                   SendToPlayer(&buf[next_out], oldi - next_out);
2202                 if (++i > next_out)
2203                   next_out = i;
2204                 continue;
2205             }
2206                 
2207             /* OK, this at least will *usually* work */
2208             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2209                 loggedOn = TRUE;
2210             }
2211             
2212             if (loggedOn && !intfSet) {
2213                 if (ics_type == ICS_ICC) {
2214                   sprintf(str,
2215                           "/set-quietly interface %s\n/set-quietly style 12\n",
2216                           programVersion);
2217                   strcat(str, "/set-quietly wrap 0\n");
2218
2219                 } else if (ics_type == ICS_CHESSNET) {
2220                   sprintf(str, "/style 12\n");
2221                 } else {
2222                   strcpy(str, "alias $ @\n$set interface ");
2223                   strcat(str, programVersion);
2224                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2225 #ifdef WIN32
2226                   strcat(str, "$iset nohighlight 1\n");
2227 #endif
2228                   strcat(str, "$iset nowrap 1\n");
2229                   strcat(str, "$iset lock 1\n$style 12\n");
2230                 }
2231                 SendToICS(str);
2232                 NotifyFrontendLogin();
2233                 intfSet = TRUE;
2234             }
2235
2236             if (started == STARTED_COMMENT) {
2237                 /* Accumulate characters in comment */
2238                 parse[parse_pos++] = buf[i];
2239                 if (buf[i] == '\n') {
2240                     parse[parse_pos] = NULLCHAR;
2241                     if(chattingPartner>=0) {
2242                         char mess[MSG_SIZ];
2243                         sprintf(mess, "%s%s", talker, parse);
2244                         OutputChatMessage(chattingPartner, mess);
2245                         chattingPartner = -1;
2246                     } else
2247                     if(!suppressKibitz) // [HGM] kibitz
2248                         AppendComment(forwardMostMove, StripHighlight(parse));
2249                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2250                         int nrDigit = 0, nrAlph = 0, i;
2251                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2252                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2253                         parse[parse_pos] = NULLCHAR;
2254                         // try to be smart: if it does not look like search info, it should go to
2255                         // ICS interaction window after all, not to engine-output window.
2256                         for(i=0; i<parse_pos; i++) { // count letters and digits
2257                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2258                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2259                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2260                         }
2261                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2262                             int depth=0; float score;
2263                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2264                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2265                                 pvInfoList[forwardMostMove-1].depth = depth;
2266                                 pvInfoList[forwardMostMove-1].score = 100*score;
2267                             }
2268                             OutputKibitz(suppressKibitz, parse);
2269                         } else {
2270                             char tmp[MSG_SIZ];
2271                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2272                             SendToPlayer(tmp, strlen(tmp));
2273                         }
2274                     }
2275                     started = STARTED_NONE;
2276                 } else {
2277                     /* Don't match patterns against characters in chatter */
2278                     i++;
2279                     continue;
2280                 }
2281             }
2282             if (started == STARTED_CHATTER) {
2283                 if (buf[i] != '\n') {
2284                     /* Don't match patterns against characters in chatter */
2285                     i++;
2286                     continue;
2287                 }
2288                 started = STARTED_NONE;
2289             }
2290
2291             /* Kludge to deal with rcmd protocol */
2292             if (firstTime && looking_at(buf, &i, "\001*")) {
2293                 DisplayFatalError(&buf[1], 0, 1);
2294                 continue;
2295             } else {
2296                 firstTime = FALSE;
2297             }
2298
2299             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2300                 ics_type = ICS_ICC;
2301                 ics_prefix = "/";
2302                 if (appData.debugMode)
2303                   fprintf(debugFP, "ics_type %d\n", ics_type);
2304                 continue;
2305             }
2306             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2307                 ics_type = ICS_FICS;
2308                 ics_prefix = "$";
2309                 if (appData.debugMode)
2310                   fprintf(debugFP, "ics_type %d\n", ics_type);
2311                 continue;
2312             }
2313             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2314                 ics_type = ICS_CHESSNET;
2315                 ics_prefix = "/";
2316                 if (appData.debugMode)
2317                   fprintf(debugFP, "ics_type %d\n", ics_type);
2318                 continue;
2319             }
2320
2321             if (!loggedOn &&
2322                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2323                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2324                  looking_at(buf, &i, "will be \"*\""))) {
2325               strcpy(ics_handle, star_match[0]);
2326               continue;
2327             }
2328
2329             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2330               char buf[MSG_SIZ];
2331               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2332               DisplayIcsInteractionTitle(buf);
2333               have_set_title = TRUE;
2334             }
2335
2336             /* skip finger notes */
2337             if (started == STARTED_NONE &&
2338                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2339                  (buf[i] == '1' && buf[i+1] == '0')) &&
2340                 buf[i+2] == ':' && buf[i+3] == ' ') {
2341               started = STARTED_CHATTER;
2342               i += 3;
2343               continue;
2344             }
2345
2346             /* skip formula vars */
2347             if (started == STARTED_NONE &&
2348                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2349               started = STARTED_CHATTER;
2350               i += 3;
2351               continue;
2352             }
2353
2354             oldi = i;
2355             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2356             if (appData.autoKibitz && started == STARTED_NONE && 
2357                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2358                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2359                 if(looking_at(buf, &i, "* kibitzes: ") &&
2360                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2361                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2362                         suppressKibitz = TRUE;
2363                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2364                                 && (gameMode == IcsPlayingWhite)) ||
2365                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2366                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2367                             started = STARTED_CHATTER; // own kibitz we simply discard
2368                         else {
2369                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2370                             parse_pos = 0; parse[0] = NULLCHAR;
2371                             savingComment = TRUE;
2372                             suppressKibitz = gameMode != IcsObserving ? 2 :
2373                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2374                         } 
2375                         continue;
2376                 } else
2377                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2378                     started = STARTED_CHATTER;
2379                     suppressKibitz = TRUE;
2380                 }
2381             } // [HGM] kibitz: end of patch
2382
2383 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2384
2385             // [HGM] chat: intercept tells by users for which we have an open chat window
2386             channel = -1;
2387             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2388                                            looking_at(buf, &i, "* whispers:") ||
2389                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2390                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2391                 int p;
2392                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2393                 chattingPartner = -1;
2394
2395                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2396                 for(p=0; p<MAX_CHAT; p++) {
2397                     if(channel == atoi(chatPartner[p])) {
2398                     talker[0] = '['; strcat(talker, "]");
2399                     chattingPartner = p; break;
2400                     }
2401                 } else
2402                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2403                 for(p=0; p<MAX_CHAT; p++) {
2404                     if(!strcmp("WHISPER", chatPartner[p])) {
2405                         talker[0] = '['; strcat(talker, "]");
2406                         chattingPartner = p; break;
2407                     }
2408                 }
2409                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2410                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2411                     talker[0] = 0;
2412                     chattingPartner = p; break;
2413                 }
2414                 if(chattingPartner<0) i = oldi; else {
2415                     started = STARTED_COMMENT;
2416                     parse_pos = 0; parse[0] = NULLCHAR;
2417                     savingComment = TRUE;
2418                     suppressKibitz = TRUE;
2419                 }
2420             } // [HGM] chat: end of patch
2421
2422             if (appData.zippyTalk || appData.zippyPlay) {
2423                 /* [DM] Backup address for color zippy lines */
2424                 backup = i;
2425 #if ZIPPY
2426        #ifdef WIN32
2427                if (loggedOn == TRUE)
2428                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2429                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2430        #else
2431                 if (ZippyControl(buf, &i) ||
2432                     ZippyConverse(buf, &i) ||
2433                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2434                       loggedOn = TRUE;
2435                       if (!appData.colorize) continue;
2436                 }
2437        #endif
2438 #endif
2439             } // [DM] 'else { ' deleted
2440                 if (
2441                     /* Regular tells and says */
2442                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2443                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2444                     looking_at(buf, &i, "* says: ") ||
2445                     /* Don't color "message" or "messages" output */
2446                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2447                     looking_at(buf, &i, "*. * at *:*: ") ||
2448                     looking_at(buf, &i, "--* (*:*): ") ||
2449                     /* Message notifications (same color as tells) */
2450                     looking_at(buf, &i, "* has left a message ") ||
2451                     looking_at(buf, &i, "* just sent you a message:\n") ||
2452                     /* Whispers and kibitzes */
2453                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2454                     looking_at(buf, &i, "* kibitzes: ") ||
2455                     /* Channel tells */
2456                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2457
2458                   if (tkind == 1 && strchr(star_match[0], ':')) {
2459                       /* Avoid "tells you:" spoofs in channels */
2460                      tkind = 3;
2461                   }
2462                   if (star_match[0][0] == NULLCHAR ||
2463                       strchr(star_match[0], ' ') ||
2464                       (tkind == 3 && strchr(star_match[1], ' '))) {
2465                     /* Reject bogus matches */
2466                     i = oldi;
2467                   } else {
2468                     if (appData.colorize) {
2469                       if (oldi > next_out) {
2470                         SendToPlayer(&buf[next_out], oldi - next_out);
2471                         next_out = oldi;
2472                       }
2473                       switch (tkind) {
2474                       case 1:
2475                         Colorize(ColorTell, FALSE);
2476                         curColor = ColorTell;
2477                         break;
2478                       case 2:
2479                         Colorize(ColorKibitz, FALSE);
2480                         curColor = ColorKibitz;
2481                         break;
2482                       case 3:
2483                         p = strrchr(star_match[1], '(');
2484                         if (p == NULL) {
2485                           p = star_match[1];
2486                         } else {
2487                           p++;
2488                         }
2489                         if (atoi(p) == 1) {
2490                           Colorize(ColorChannel1, FALSE);
2491                           curColor = ColorChannel1;
2492                         } else {
2493                           Colorize(ColorChannel, FALSE);
2494                           curColor = ColorChannel;
2495                         }
2496                         break;
2497                       case 5:
2498                         curColor = ColorNormal;
2499                         break;
2500                       }
2501                     }
2502                     if (started == STARTED_NONE && appData.autoComment &&
2503                         (gameMode == IcsObserving ||
2504                          gameMode == IcsPlayingWhite ||
2505                          gameMode == IcsPlayingBlack)) {
2506                       parse_pos = i - oldi;
2507                       memcpy(parse, &buf[oldi], parse_pos);
2508                       parse[parse_pos] = NULLCHAR;
2509                       started = STARTED_COMMENT;
2510                       savingComment = TRUE;
2511                     } else {
2512                       started = STARTED_CHATTER;
2513                       savingComment = FALSE;
2514                     }
2515                     loggedOn = TRUE;
2516                     continue;
2517                   }
2518                 }
2519
2520                 if (looking_at(buf, &i, "* s-shouts: ") ||
2521                     looking_at(buf, &i, "* c-shouts: ")) {
2522                     if (appData.colorize) {
2523                         if (oldi > next_out) {
2524                             SendToPlayer(&buf[next_out], oldi - next_out);
2525                             next_out = oldi;
2526                         }
2527                         Colorize(ColorSShout, FALSE);
2528                         curColor = ColorSShout;
2529                     }
2530                     loggedOn = TRUE;
2531                     started = STARTED_CHATTER;
2532                     continue;
2533                 }
2534
2535                 if (looking_at(buf, &i, "--->")) {
2536                     loggedOn = TRUE;
2537                     continue;
2538                 }
2539
2540                 if (looking_at(buf, &i, "* shouts: ") ||
2541                     looking_at(buf, &i, "--> ")) {
2542                     if (appData.colorize) {
2543                         if (oldi > next_out) {
2544                             SendToPlayer(&buf[next_out], oldi - next_out);
2545                             next_out = oldi;
2546                         }
2547                         Colorize(ColorShout, FALSE);
2548                         curColor = ColorShout;
2549                     }
2550                     loggedOn = TRUE;
2551                     started = STARTED_CHATTER;
2552                     continue;
2553                 }
2554
2555                 if (looking_at( buf, &i, "Challenge:")) {
2556                     if (appData.colorize) {
2557                         if (oldi > next_out) {
2558                             SendToPlayer(&buf[next_out], oldi - next_out);
2559                             next_out = oldi;
2560                         }
2561                         Colorize(ColorChallenge, FALSE);
2562                         curColor = ColorChallenge;
2563                     }
2564                     loggedOn = TRUE;
2565                     continue;
2566                 }
2567
2568                 if (looking_at(buf, &i, "* offers you") ||
2569                     looking_at(buf, &i, "* offers to be") ||
2570                     looking_at(buf, &i, "* would like to") ||
2571                     looking_at(buf, &i, "* requests to") ||
2572                     looking_at(buf, &i, "Your opponent offers") ||
2573                     looking_at(buf, &i, "Your opponent requests")) {
2574
2575                     if (appData.colorize) {
2576                         if (oldi > next_out) {
2577                             SendToPlayer(&buf[next_out], oldi - next_out);
2578                             next_out = oldi;
2579                         }
2580                         Colorize(ColorRequest, FALSE);
2581                         curColor = ColorRequest;
2582                     }
2583                     continue;
2584                 }
2585
2586                 if (looking_at(buf, &i, "* (*) seeking")) {
2587                     if (appData.colorize) {
2588                         if (oldi > next_out) {
2589                             SendToPlayer(&buf[next_out], oldi - next_out);
2590                             next_out = oldi;
2591                         }
2592                         Colorize(ColorSeek, FALSE);
2593                         curColor = ColorSeek;
2594                     }
2595                     continue;
2596             }
2597
2598             if (looking_at(buf, &i, "\\   ")) {
2599                 if (prevColor != ColorNormal) {
2600                     if (oldi > next_out) {
2601                         SendToPlayer(&buf[next_out], oldi - next_out);
2602                         next_out = oldi;
2603                     }
2604                     Colorize(prevColor, TRUE);
2605                     curColor = prevColor;
2606                 }
2607                 if (savingComment) {
2608                     parse_pos = i - oldi;
2609                     memcpy(parse, &buf[oldi], parse_pos);
2610                     parse[parse_pos] = NULLCHAR;
2611                     started = STARTED_COMMENT;
2612                 } else {
2613                     started = STARTED_CHATTER;
2614                 }
2615                 continue;
2616             }
2617
2618             if (looking_at(buf, &i, "Black Strength :") ||
2619                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2620                 looking_at(buf, &i, "<10>") ||
2621                 looking_at(buf, &i, "#@#")) {
2622                 /* Wrong board style */
2623                 loggedOn = TRUE;
2624                 SendToICS(ics_prefix);
2625                 SendToICS("set style 12\n");
2626                 SendToICS(ics_prefix);
2627                 SendToICS("refresh\n");
2628                 continue;
2629             }
2630             
2631             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2632                 ICSInitScript();
2633                 have_sent_ICS_logon = 1;
2634                 continue;
2635             }
2636               
2637             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2638                 (looking_at(buf, &i, "\n<12> ") ||
2639                  looking_at(buf, &i, "<12> "))) {
2640                 loggedOn = TRUE;
2641                 if (oldi > next_out) {
2642                     SendToPlayer(&buf[next_out], oldi - next_out);
2643                 }
2644                 next_out = i;
2645                 started = STARTED_BOARD;
2646                 parse_pos = 0;
2647                 continue;
2648             }
2649
2650             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2651                 looking_at(buf, &i, "<b1> ")) {
2652                 if (oldi > next_out) {
2653                     SendToPlayer(&buf[next_out], oldi - next_out);
2654                 }
2655                 next_out = i;
2656                 started = STARTED_HOLDINGS;
2657                 parse_pos = 0;
2658                 continue;
2659             }
2660
2661             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2662                 loggedOn = TRUE;
2663                 /* Header for a move list -- first line */
2664
2665                 switch (ics_getting_history) {
2666                   case H_FALSE:
2667                     switch (gameMode) {
2668                       case IcsIdle:
2669                       case BeginningOfGame:
2670                         /* User typed "moves" or "oldmoves" while we
2671                            were idle.  Pretend we asked for these
2672                            moves and soak them up so user can step
2673                            through them and/or save them.
2674                            */
2675                         Reset(FALSE, TRUE);
2676                         gameMode = IcsObserving;
2677                         ModeHighlight();
2678                         ics_gamenum = -1;
2679                         ics_getting_history = H_GOT_UNREQ_HEADER;
2680                         break;
2681                       case EditGame: /*?*/
2682                       case EditPosition: /*?*/
2683                         /* Should above feature work in these modes too? */
2684                         /* For now it doesn't */
2685                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2686                         break;
2687                       default:
2688                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2689                         break;
2690                     }
2691                     break;
2692                   case H_REQUESTED:
2693                     /* Is this the right one? */
2694                     if (gameInfo.white && gameInfo.black &&
2695                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2696                         strcmp(gameInfo.black, star_match[2]) == 0) {
2697                         /* All is well */
2698                         ics_getting_history = H_GOT_REQ_HEADER;
2699                     }
2700                     break;
2701                   case H_GOT_REQ_HEADER:
2702                   case H_GOT_UNREQ_HEADER:
2703                   case H_GOT_UNWANTED_HEADER:
2704                   case H_GETTING_MOVES:
2705                     /* Should not happen */
2706                     DisplayError(_("Error gathering move list: two headers"), 0);
2707                     ics_getting_history = H_FALSE;
2708                     break;
2709                 }
2710
2711                 /* Save player ratings into gameInfo if needed */
2712                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2713                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2714                     (gameInfo.whiteRating == -1 ||
2715                      gameInfo.blackRating == -1)) {
2716
2717                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2718                     gameInfo.blackRating = string_to_rating(star_match[3]);
2719                     if (appData.debugMode)
2720                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2721                               gameInfo.whiteRating, gameInfo.blackRating);
2722                 }
2723                 continue;
2724             }
2725
2726             if (looking_at(buf, &i,
2727               "* * match, initial time: * minute*, increment: * second")) {
2728                 /* Header for a move list -- second line */
2729                 /* Initial board will follow if this is a wild game */
2730                 if (gameInfo.event != NULL) free(gameInfo.event);
2731                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2732                 gameInfo.event = StrSave(str);
2733                 /* [HGM] we switched variant. Translate boards if needed. */
2734                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2735                 continue;
2736             }
2737
2738             if (looking_at(buf, &i, "Move  ")) {
2739                 /* Beginning of a move list */
2740                 switch (ics_getting_history) {
2741                   case H_FALSE:
2742                     /* Normally should not happen */
2743                     /* Maybe user hit reset while we were parsing */
2744                     break;
2745                   case H_REQUESTED:
2746                     /* Happens if we are ignoring a move list that is not
2747                      * the one we just requested.  Common if the user
2748                      * tries to observe two games without turning off
2749                      * getMoveList */
2750                     break;
2751                   case H_GETTING_MOVES:
2752                     /* Should not happen */
2753                     DisplayError(_("Error gathering move list: nested"), 0);
2754                     ics_getting_history = H_FALSE;
2755                     break;
2756                   case H_GOT_REQ_HEADER:
2757                     ics_getting_history = H_GETTING_MOVES;
2758                     started = STARTED_MOVES;
2759                     parse_pos = 0;
2760                     if (oldi > next_out) {
2761                         SendToPlayer(&buf[next_out], oldi - next_out);
2762                     }
2763                     break;
2764                   case H_GOT_UNREQ_HEADER:
2765                     ics_getting_history = H_GETTING_MOVES;
2766                     started = STARTED_MOVES_NOHIDE;
2767                     parse_pos = 0;
2768                     break;
2769                   case H_GOT_UNWANTED_HEADER:
2770                     ics_getting_history = H_FALSE;
2771                     break;
2772                 }
2773                 continue;
2774             }                           
2775             
2776             if (looking_at(buf, &i, "% ") ||
2777                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2778                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2779                 savingComment = FALSE;
2780                 switch (started) {
2781                   case STARTED_MOVES:
2782                   case STARTED_MOVES_NOHIDE:
2783                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2784                     parse[parse_pos + i - oldi] = NULLCHAR;
2785                     ParseGameHistory(parse);
2786 #if ZIPPY
2787                     if (appData.zippyPlay && first.initDone) {
2788                         FeedMovesToProgram(&first, forwardMostMove);
2789                         if (gameMode == IcsPlayingWhite) {
2790                             if (WhiteOnMove(forwardMostMove)) {
2791                                 if (first.sendTime) {
2792                                   if (first.useColors) {
2793                                     SendToProgram("black\n", &first); 
2794                                   }
2795                                   SendTimeRemaining(&first, TRUE);
2796                                 }
2797                                 if (first.useColors) {
2798                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2799                                 }
2800                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2801                                 first.maybeThinking = TRUE;
2802                             } else {
2803                                 if (first.usePlayother) {
2804                                   if (first.sendTime) {
2805                                     SendTimeRemaining(&first, TRUE);
2806                                   }
2807                                   SendToProgram("playother\n", &first);
2808                                   firstMove = FALSE;
2809                                 } else {
2810                                   firstMove = TRUE;
2811                                 }
2812                             }
2813                         } else if (gameMode == IcsPlayingBlack) {
2814                             if (!WhiteOnMove(forwardMostMove)) {
2815                                 if (first.sendTime) {
2816                                   if (first.useColors) {
2817                                     SendToProgram("white\n", &first);
2818                                   }
2819                                   SendTimeRemaining(&first, FALSE);
2820                                 }
2821                                 if (first.useColors) {
2822                                   SendToProgram("black\n", &first);
2823                                 }
2824                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2825                                 first.maybeThinking = TRUE;
2826                             } else {
2827                                 if (first.usePlayother) {
2828                                   if (first.sendTime) {
2829                                     SendTimeRemaining(&first, FALSE);
2830                                   }
2831                                   SendToProgram("playother\n", &first);
2832                                   firstMove = FALSE;
2833                                 } else {
2834                                   firstMove = TRUE;
2835                                 }
2836                             }
2837                         }                       
2838                     }
2839 #endif
2840                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2841                         /* Moves came from oldmoves or moves command
2842                            while we weren't doing anything else.
2843                            */
2844                         currentMove = forwardMostMove;
2845                         ClearHighlights();/*!!could figure this out*/
2846                         flipView = appData.flipView;
2847                         DrawPosition(FALSE, boards[currentMove]);
2848                         DisplayBothClocks();
2849                         sprintf(str, "%s vs. %s",
2850                                 gameInfo.white, gameInfo.black);
2851                         DisplayTitle(str);
2852                         gameMode = IcsIdle;
2853                     } else {
2854                         /* Moves were history of an active game */
2855                         if (gameInfo.resultDetails != NULL) {
2856                             free(gameInfo.resultDetails);
2857                             gameInfo.resultDetails = NULL;
2858                         }
2859                     }
2860                     HistorySet(parseList, backwardMostMove,
2861                                forwardMostMove, currentMove-1);
2862                     DisplayMove(currentMove - 1);
2863                     if (started == STARTED_MOVES) next_out = i;
2864                     started = STARTED_NONE;
2865                     ics_getting_history = H_FALSE;
2866                     break;
2867
2868                   case STARTED_OBSERVE:
2869                     started = STARTED_NONE;
2870                     SendToICS(ics_prefix);
2871                     SendToICS("refresh\n");
2872                     break;
2873
2874                   default:
2875                     break;
2876                 }
2877                 if(bookHit) { // [HGM] book: simulate book reply
2878                     static char bookMove[MSG_SIZ]; // a bit generous?
2879
2880                     programStats.nodes = programStats.depth = programStats.time = 
2881                     programStats.score = programStats.got_only_move = 0;
2882                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2883
2884                     strcpy(bookMove, "move ");
2885                     strcat(bookMove, bookHit);
2886                     HandleMachineMove(bookMove, &first);
2887                 }
2888                 continue;
2889             }
2890             
2891             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2892                  started == STARTED_HOLDINGS ||
2893                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2894                 /* Accumulate characters in move list or board */
2895                 parse[parse_pos++] = buf[i];
2896             }
2897             
2898             /* Start of game messages.  Mostly we detect start of game
2899                when the first board image arrives.  On some versions
2900                of the ICS, though, we need to do a "refresh" after starting
2901                to observe in order to get the current board right away. */
2902             if (looking_at(buf, &i, "Adding game * to observation list")) {
2903                 started = STARTED_OBSERVE;
2904                 continue;
2905             }
2906
2907             /* Handle auto-observe */
2908             if (appData.autoObserve &&
2909                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2910                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2911                 char *player;
2912                 /* Choose the player that was highlighted, if any. */
2913                 if (star_match[0][0] == '\033' ||
2914                     star_match[1][0] != '\033') {
2915                     player = star_match[0];
2916                 } else {
2917                     player = star_match[2];
2918                 }
2919                 sprintf(str, "%sobserve %s\n",
2920                         ics_prefix, StripHighlightAndTitle(player));
2921                 SendToICS(str);
2922
2923                 /* Save ratings from notify string */
2924                 strcpy(player1Name, star_match[0]);
2925                 player1Rating = string_to_rating(star_match[1]);
2926                 strcpy(player2Name, star_match[2]);
2927                 player2Rating = string_to_rating(star_match[3]);
2928
2929                 if (appData.debugMode)
2930                   fprintf(debugFP, 
2931                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2932                           player1Name, player1Rating,
2933                           player2Name, player2Rating);
2934
2935                 continue;
2936             }
2937
2938             /* Deal with automatic examine mode after a game,
2939                and with IcsObserving -> IcsExamining transition */
2940             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2941                 looking_at(buf, &i, "has made you an examiner of game *")) {
2942
2943                 int gamenum = atoi(star_match[0]);
2944                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2945                     gamenum == ics_gamenum) {
2946                     /* We were already playing or observing this game;
2947                        no need to refetch history */
2948                     gameMode = IcsExamining;
2949                     if (pausing) {
2950                         pauseExamForwardMostMove = forwardMostMove;
2951                     } else if (currentMove < forwardMostMove) {
2952                         ForwardInner(forwardMostMove);
2953                     }
2954                 } else {
2955                     /* I don't think this case really can happen */
2956                     SendToICS(ics_prefix);
2957                     SendToICS("refresh\n");
2958                 }
2959                 continue;
2960             }    
2961             
2962             /* Error messages */
2963 //          if (ics_user_moved) {
2964             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2965                 if (looking_at(buf, &i, "Illegal move") ||
2966                     looking_at(buf, &i, "Not a legal move") ||
2967                     looking_at(buf, &i, "Your king is in check") ||
2968                     looking_at(buf, &i, "It isn't your turn") ||
2969                     looking_at(buf, &i, "It is not your move")) {
2970                     /* Illegal move */
2971                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2972                         currentMove = --forwardMostMove;
2973                         DisplayMove(currentMove - 1); /* before DMError */
2974                         DrawPosition(FALSE, boards[currentMove]);
2975                         SwitchClocks();
2976                         DisplayBothClocks();
2977                     }
2978                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2979                     ics_user_moved = 0;
2980                     continue;
2981                 }
2982             }
2983
2984             if (looking_at(buf, &i, "still have time") ||
2985                 looking_at(buf, &i, "not out of time") ||
2986                 looking_at(buf, &i, "either player is out of time") ||
2987                 looking_at(buf, &i, "has timeseal; checking")) {
2988                 /* We must have called his flag a little too soon */
2989                 whiteFlag = blackFlag = FALSE;
2990                 continue;
2991             }
2992
2993             if (looking_at(buf, &i, "added * seconds to") ||
2994                 looking_at(buf, &i, "seconds were added to")) {
2995                 /* Update the clocks */
2996                 SendToICS(ics_prefix);
2997                 SendToICS("refresh\n");
2998                 continue;
2999             }
3000
3001             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3002                 ics_clock_paused = TRUE;
3003                 StopClocks();
3004                 continue;
3005             }
3006
3007             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3008                 ics_clock_paused = FALSE;
3009                 StartClocks();
3010                 continue;
3011             }
3012
3013             /* Grab player ratings from the Creating: message.
3014                Note we have to check for the special case when
3015                the ICS inserts things like [white] or [black]. */
3016             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3017                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3018                 /* star_matches:
3019                    0    player 1 name (not necessarily white)
3020                    1    player 1 rating
3021                    2    empty, white, or black (IGNORED)
3022                    3    player 2 name (not necessarily black)
3023                    4    player 2 rating
3024                    
3025                    The names/ratings are sorted out when the game
3026                    actually starts (below).
3027                 */
3028                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3029                 player1Rating = string_to_rating(star_match[1]);
3030                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3031                 player2Rating = string_to_rating(star_match[4]);
3032
3033                 if (appData.debugMode)
3034                   fprintf(debugFP, 
3035                           "Ratings from 'Creating:' %s %d, %s %d\n",
3036                           player1Name, player1Rating,
3037                           player2Name, player2Rating);
3038
3039                 continue;
3040             }
3041             
3042             /* Improved generic start/end-of-game messages */
3043             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3044                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3045                 /* If tkind == 0: */
3046                 /* star_match[0] is the game number */
3047                 /*           [1] is the white player's name */
3048                 /*           [2] is the black player's name */
3049                 /* For end-of-game: */
3050                 /*           [3] is the reason for the game end */
3051                 /*           [4] is a PGN end game-token, preceded by " " */
3052                 /* For start-of-game: */
3053                 /*           [3] begins with "Creating" or "Continuing" */
3054                 /*           [4] is " *" or empty (don't care). */
3055                 int gamenum = atoi(star_match[0]);
3056                 char *whitename, *blackname, *why, *endtoken;
3057                 ChessMove endtype = (ChessMove) 0;
3058
3059                 if (tkind == 0) {
3060                   whitename = star_match[1];
3061                   blackname = star_match[2];
3062                   why = star_match[3];
3063                   endtoken = star_match[4];
3064                 } else {
3065                   whitename = star_match[1];
3066                   blackname = star_match[3];
3067                   why = star_match[5];
3068                   endtoken = star_match[6];
3069                 }
3070
3071                 /* Game start messages */
3072                 if (strncmp(why, "Creating ", 9) == 0 ||
3073                     strncmp(why, "Continuing ", 11) == 0) {
3074                     gs_gamenum = gamenum;
3075                     strcpy(gs_kind, strchr(why, ' ') + 1);
3076 #if ZIPPY
3077                     if (appData.zippyPlay) {
3078                         ZippyGameStart(whitename, blackname);
3079                     }
3080 #endif /*ZIPPY*/
3081                     continue;
3082                 }
3083
3084                 /* Game end messages */
3085                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3086                     ics_gamenum != gamenum) {
3087                     continue;
3088                 }
3089                 while (endtoken[0] == ' ') endtoken++;
3090                 switch (endtoken[0]) {
3091                   case '*':
3092                   default:
3093                     endtype = GameUnfinished;
3094                     break;
3095                   case '0':
3096                     endtype = BlackWins;
3097                     break;
3098                   case '1':
3099                     if (endtoken[1] == '/')
3100                       endtype = GameIsDrawn;
3101                     else
3102                       endtype = WhiteWins;
3103                     break;
3104                 }
3105                 GameEnds(endtype, why, GE_ICS);
3106 #if ZIPPY
3107                 if (appData.zippyPlay && first.initDone) {
3108                     ZippyGameEnd(endtype, why);
3109                     if (first.pr == NULL) {
3110                       /* Start the next process early so that we'll
3111                          be ready for the next challenge */
3112                       StartChessProgram(&first);
3113                     }
3114                     /* Send "new" early, in case this command takes
3115                        a long time to finish, so that we'll be ready
3116                        for the next challenge. */
3117                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3118                     Reset(TRUE, TRUE);
3119                 }
3120 #endif /*ZIPPY*/
3121                 continue;
3122             }
3123
3124             if (looking_at(buf, &i, "Removing game * from observation") ||
3125                 looking_at(buf, &i, "no longer observing game *") ||
3126                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3127                 if (gameMode == IcsObserving &&
3128                     atoi(star_match[0]) == ics_gamenum)
3129                   {
3130                       /* icsEngineAnalyze */
3131                       if (appData.icsEngineAnalyze) {
3132                             ExitAnalyzeMode();
3133                             ModeHighlight();
3134                       }
3135                       StopClocks();
3136                       gameMode = IcsIdle;
3137                       ics_gamenum = -1;
3138                       ics_user_moved = FALSE;
3139                   }
3140                 continue;
3141             }
3142
3143             if (looking_at(buf, &i, "no longer examining game *")) {
3144                 if (gameMode == IcsExamining &&
3145                     atoi(star_match[0]) == ics_gamenum)
3146                   {
3147                       gameMode = IcsIdle;
3148                       ics_gamenum = -1;
3149                       ics_user_moved = FALSE;
3150                   }
3151                 continue;
3152             }
3153
3154             /* Advance leftover_start past any newlines we find,
3155                so only partial lines can get reparsed */
3156             if (looking_at(buf, &i, "\n")) {
3157                 prevColor = curColor;
3158                 if (curColor != ColorNormal) {
3159                     if (oldi > next_out) {
3160                         SendToPlayer(&buf[next_out], oldi - next_out);
3161                         next_out = oldi;
3162                     }
3163                     Colorize(ColorNormal, FALSE);
3164                     curColor = ColorNormal;
3165                 }
3166                 if (started == STARTED_BOARD) {
3167                     started = STARTED_NONE;
3168                     parse[parse_pos] = NULLCHAR;
3169                     ParseBoard12(parse);
3170                     ics_user_moved = 0;
3171
3172                     /* Send premove here */
3173                     if (appData.premove) {
3174                       char str[MSG_SIZ];
3175                       if (currentMove == 0 &&
3176                           gameMode == IcsPlayingWhite &&
3177                           appData.premoveWhite) {
3178                         sprintf(str, "%s%s\n", ics_prefix,
3179                                 appData.premoveWhiteText);
3180                         if (appData.debugMode)
3181                           fprintf(debugFP, "Sending premove:\n");
3182                         SendToICS(str);
3183                       } else if (currentMove == 1 &&
3184                                  gameMode == IcsPlayingBlack &&
3185                                  appData.premoveBlack) {
3186                         sprintf(str, "%s%s\n", ics_prefix,
3187                                 appData.premoveBlackText);
3188                         if (appData.debugMode)
3189                           fprintf(debugFP, "Sending premove:\n");
3190                         SendToICS(str);
3191                       } else if (gotPremove) {
3192                         gotPremove = 0;
3193                         ClearPremoveHighlights();
3194                         if (appData.debugMode)
3195                           fprintf(debugFP, "Sending premove:\n");
3196                           UserMoveEvent(premoveFromX, premoveFromY, 
3197                                         premoveToX, premoveToY, 
3198                                         premovePromoChar);
3199                       }
3200                     }
3201
3202                     /* Usually suppress following prompt */
3203                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3204                         if (looking_at(buf, &i, "*% ")) {
3205                             savingComment = FALSE;
3206                         }
3207                     }
3208                     next_out = i;
3209                 } else if (started == STARTED_HOLDINGS) {
3210                     int gamenum;
3211                     char new_piece[MSG_SIZ];
3212                     started = STARTED_NONE;
3213                     parse[parse_pos] = NULLCHAR;
3214                     if (appData.debugMode)
3215                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3216                                                         parse, currentMove);
3217                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3218                         gamenum == ics_gamenum) {
3219                         if (gameInfo.variant == VariantNormal) {
3220                           /* [HGM] We seem to switch variant during a game!
3221                            * Presumably no holdings were displayed, so we have
3222                            * to move the position two files to the right to
3223                            * create room for them!
3224                            */
3225                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3226                           /* Get a move list just to see the header, which
3227                              will tell us whether this is really bug or zh */
3228                           if (ics_getting_history == H_FALSE) {
3229                             ics_getting_history = H_REQUESTED;
3230                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3231                             SendToICS(str);
3232                           }
3233                         }
3234                         new_piece[0] = NULLCHAR;
3235                         sscanf(parse, "game %d white [%s black [%s <- %s",
3236                                &gamenum, white_holding, black_holding,
3237                                new_piece);
3238                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3239                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3240                         /* [HGM] copy holdings to board holdings area */
3241                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3242                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3243 #if ZIPPY
3244                         if (appData.zippyPlay && first.initDone) {
3245                             ZippyHoldings(white_holding, black_holding,
3246                                           new_piece);
3247                         }
3248 #endif /*ZIPPY*/
3249                         if (tinyLayout || smallLayout) {
3250                             char wh[16], bh[16];
3251                             PackHolding(wh, white_holding);
3252                             PackHolding(bh, black_holding);
3253                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3254                                     gameInfo.white, gameInfo.black);
3255                         } else {
3256                             sprintf(str, "%s [%s] vs. %s [%s]",
3257                                     gameInfo.white, white_holding,
3258                                     gameInfo.black, black_holding);
3259                         }
3260
3261                         DrawPosition(FALSE, boards[currentMove]);
3262                         DisplayTitle(str);
3263                     }
3264                     /* Suppress following prompt */
3265                     if (looking_at(buf, &i, "*% ")) {
3266                         savingComment = FALSE;
3267                     }
3268                     next_out = i;
3269                 }
3270                 continue;
3271             }
3272
3273             i++;                /* skip unparsed character and loop back */
3274         }
3275         
3276         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3277             started != STARTED_HOLDINGS && i > next_out) {
3278             SendToPlayer(&buf[next_out], i - next_out);
3279             next_out = i;
3280         }
3281         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3282         
3283         leftover_len = buf_len - leftover_start;
3284         /* if buffer ends with something we couldn't parse,
3285            reparse it after appending the next read */
3286         
3287     } else if (count == 0) {
3288         RemoveInputSource(isr);
3289         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3290     } else {
3291         DisplayFatalError(_("Error reading from ICS"), error, 1);
3292     }
3293 }
3294
3295
3296 /* Board style 12 looks like this:
3297    
3298    <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
3299    
3300  * The "<12> " is stripped before it gets to this routine.  The two
3301  * trailing 0's (flip state and clock ticking) are later addition, and
3302  * some chess servers may not have them, or may have only the first.
3303  * Additional trailing fields may be added in the future.  
3304  */
3305
3306 #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"
3307
3308 #define RELATION_OBSERVING_PLAYED    0
3309 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3310 #define RELATION_PLAYING_MYMOVE      1
3311 #define RELATION_PLAYING_NOTMYMOVE  -1
3312 #define RELATION_EXAMINING           2
3313 #define RELATION_ISOLATED_BOARD     -3
3314 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3315
3316 void
3317 ParseBoard12(string)
3318      char *string;
3319
3320     GameMode newGameMode;
3321     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3322     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3323     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3324     char to_play, board_chars[200];
3325     char move_str[500], str[500], elapsed_time[500];
3326     char black[32], white[32];
3327     Board board;
3328     int prevMove = currentMove;
3329     int ticking = 2;
3330     ChessMove moveType;
3331     int fromX, fromY, toX, toY;
3332     char promoChar;
3333     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3334     char *bookHit = NULL; // [HGM] book
3335
3336     fromX = fromY = toX = toY = -1;
3337     
3338     newGame = FALSE;
3339
3340     if (appData.debugMode)
3341       fprintf(debugFP, _("Parsing board: %s\n"), string);
3342
3343     move_str[0] = NULLCHAR;
3344     elapsed_time[0] = NULLCHAR;
3345     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3346         int  i = 0, j;
3347         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3348             if(string[i] == ' ') { ranks++; files = 0; }
3349             else files++;
3350             i++;
3351         }
3352         for(j = 0; j <i; j++) board_chars[j] = string[j];
3353         board_chars[i] = '\0';
3354         string += i + 1;
3355     }
3356     n = sscanf(string, PATTERN, &to_play, &double_push,
3357                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3358                &gamenum, white, black, &relation, &basetime, &increment,
3359                &white_stren, &black_stren, &white_time, &black_time,
3360                &moveNum, str, elapsed_time, move_str, &ics_flip,
3361                &ticking);
3362
3363     if (n < 21) {
3364         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3365         DisplayError(str, 0);
3366         return;
3367     }
3368
3369     /* Convert the move number to internal form */
3370     moveNum = (moveNum - 1) * 2;
3371     if (to_play == 'B') moveNum++;
3372     if (moveNum >= MAX_MOVES) {
3373       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3374                         0, 1);
3375       return;
3376     }
3377     
3378     switch (relation) {
3379       case RELATION_OBSERVING_PLAYED:
3380       case RELATION_OBSERVING_STATIC:
3381         if (gamenum == -1) {
3382             /* Old ICC buglet */
3383             relation = RELATION_OBSERVING_STATIC;
3384         }
3385         newGameMode = IcsObserving;
3386         break;
3387       case RELATION_PLAYING_MYMOVE:
3388       case RELATION_PLAYING_NOTMYMOVE:
3389         newGameMode =
3390           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3391             IcsPlayingWhite : IcsPlayingBlack;
3392         break;
3393       case RELATION_EXAMINING:
3394         newGameMode = IcsExamining;
3395         break;
3396       case RELATION_ISOLATED_BOARD:
3397       default:
3398         /* Just display this board.  If user was doing something else,
3399            we will forget about it until the next board comes. */ 
3400         newGameMode = IcsIdle;
3401         break;
3402       case RELATION_STARTING_POSITION:
3403         newGameMode = gameMode;
3404         break;
3405     }
3406     
3407     /* Modify behavior for initial board display on move listing
3408        of wild games.
3409        */
3410     switch (ics_getting_history) {
3411       case H_FALSE:
3412       case H_REQUESTED:
3413         break;
3414       case H_GOT_REQ_HEADER:
3415       case H_GOT_UNREQ_HEADER:
3416         /* This is the initial position of the current game */
3417         gamenum = ics_gamenum;
3418         moveNum = 0;            /* old ICS bug workaround */
3419         if (to_play == 'B') {
3420           startedFromSetupPosition = TRUE;
3421           blackPlaysFirst = TRUE;
3422           moveNum = 1;
3423           if (forwardMostMove == 0) forwardMostMove = 1;
3424           if (backwardMostMove == 0) backwardMostMove = 1;
3425           if (currentMove == 0) currentMove = 1;
3426         }
3427         newGameMode = gameMode;
3428         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3429         break;
3430       case H_GOT_UNWANTED_HEADER:
3431         /* This is an initial board that we don't want */
3432         return;
3433       case H_GETTING_MOVES:
3434         /* Should not happen */
3435         DisplayError(_("Error gathering move list: extra board"), 0);
3436         ics_getting_history = H_FALSE;
3437         return;
3438     }
3439     
3440     /* Take action if this is the first board of a new game, or of a
3441        different game than is currently being displayed.  */
3442     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3443         relation == RELATION_ISOLATED_BOARD) {
3444         
3445         /* Forget the old game and get the history (if any) of the new one */
3446         if (gameMode != BeginningOfGame) {
3447           Reset(FALSE, TRUE);
3448         }
3449         newGame = TRUE;
3450         if (appData.autoRaiseBoard) BoardToTop();
3451         prevMove = -3;
3452         if (gamenum == -1) {
3453             newGameMode = IcsIdle;
3454         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3455                    appData.getMoveList) {
3456             /* Need to get game history */
3457             ics_getting_history = H_REQUESTED;
3458             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3459             SendToICS(str);
3460         }
3461         
3462         /* Initially flip the board to have black on the bottom if playing
3463            black or if the ICS flip flag is set, but let the user change
3464            it with the Flip View button. */
3465         flipView = appData.autoFlipView ? 
3466           (newGameMode == IcsPlayingBlack) || ics_flip :
3467           appData.flipView;
3468         
3469         /* Done with values from previous mode; copy in new ones */
3470         gameMode = newGameMode;
3471         ModeHighlight();
3472         ics_gamenum = gamenum;
3473         if (gamenum == gs_gamenum) {
3474             int klen = strlen(gs_kind);
3475             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3476             sprintf(str, "ICS %s", gs_kind);
3477             gameInfo.event = StrSave(str);
3478         } else {
3479             gameInfo.event = StrSave("ICS game");
3480         }
3481         gameInfo.site = StrSave(appData.icsHost);
3482         gameInfo.date = PGNDate();
3483         gameInfo.round = StrSave("-");
3484         gameInfo.white = StrSave(white);
3485         gameInfo.black = StrSave(black);
3486         timeControl = basetime * 60 * 1000;
3487         timeControl_2 = 0;
3488         timeIncrement = increment * 1000;
3489         movesPerSession = 0;
3490         gameInfo.timeControl = TimeControlTagValue();
3491         VariantSwitch(board, StringToVariant(gameInfo.event) );
3492   if (appData.debugMode) {
3493     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3494     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3495     setbuf(debugFP, NULL);
3496   }
3497
3498         gameInfo.outOfBook = NULL;
3499         
3500         /* Do we have the ratings? */
3501         if (strcmp(player1Name, white) == 0 &&
3502             strcmp(player2Name, black) == 0) {
3503             if (appData.debugMode)
3504               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3505                       player1Rating, player2Rating);
3506             gameInfo.whiteRating = player1Rating;
3507             gameInfo.blackRating = player2Rating;
3508         } else if (strcmp(player2Name, white) == 0 &&
3509                    strcmp(player1Name, black) == 0) {
3510             if (appData.debugMode)
3511               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3512                       player2Rating, player1Rating);
3513             gameInfo.whiteRating = player2Rating;
3514             gameInfo.blackRating = player1Rating;
3515         }
3516         player1Name[0] = player2Name[0] = NULLCHAR;
3517
3518         /* Silence shouts if requested */
3519         if (appData.quietPlay &&
3520             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3521             SendToICS(ics_prefix);
3522             SendToICS("set shout 0\n");
3523         }
3524     }
3525     
3526     /* Deal with midgame name changes */
3527     if (!newGame) {
3528         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3529             if (gameInfo.white) free(gameInfo.white);
3530             gameInfo.white = StrSave(white);
3531         }
3532         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3533             if (gameInfo.black) free(gameInfo.black);
3534             gameInfo.black = StrSave(black);
3535         }
3536     }
3537     
3538     /* Throw away game result if anything actually changes in examine mode */
3539     if (gameMode == IcsExamining && !newGame) {
3540         gameInfo.result = GameUnfinished;
3541         if (gameInfo.resultDetails != NULL) {
3542             free(gameInfo.resultDetails);
3543             gameInfo.resultDetails = NULL;
3544         }
3545     }
3546     
3547     /* In pausing && IcsExamining mode, we ignore boards coming
3548        in if they are in a different variation than we are. */
3549     if (pauseExamInvalid) return;
3550     if (pausing && gameMode == IcsExamining) {
3551         if (moveNum <= pauseExamForwardMostMove) {
3552             pauseExamInvalid = TRUE;
3553             forwardMostMove = pauseExamForwardMostMove;
3554             return;
3555         }
3556     }
3557     
3558   if (appData.debugMode) {
3559     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3560   }
3561     /* Parse the board */
3562     for (k = 0; k < ranks; k++) {
3563       for (j = 0; j < files; j++)
3564         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3565       if(gameInfo.holdingsWidth > 1) {
3566            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3567            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3568       }
3569     }
3570     CopyBoard(boards[moveNum], board);
3571     if (moveNum == 0) {
3572         startedFromSetupPosition =
3573           !CompareBoards(board, initialPosition);
3574         if(startedFromSetupPosition)
3575             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3576     }
3577
3578     /* [HGM] Set castling rights. Take the outermost Rooks,
3579        to make it also work for FRC opening positions. Note that board12
3580        is really defective for later FRC positions, as it has no way to
3581        indicate which Rook can castle if they are on the same side of King.
3582        For the initial position we grant rights to the outermost Rooks,
3583        and remember thos rights, and we then copy them on positions
3584        later in an FRC game. This means WB might not recognize castlings with
3585        Rooks that have moved back to their original position as illegal,
3586        but in ICS mode that is not its job anyway.
3587     */
3588     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3589     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3590
3591         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3592             if(board[0][i] == WhiteRook) j = i;
3593         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3594         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3595             if(board[0][i] == WhiteRook) j = i;
3596         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3598             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3599         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3600         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3601             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3602         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3603
3604         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3605         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3606             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3607         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3608             if(board[BOARD_HEIGHT-1][k] == bKing)
3609                 initialRights[5] = castlingRights[moveNum][5] = k;
3610     } else { int r;
3611         r = castlingRights[moveNum][0] = initialRights[0];
3612         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3613         r = castlingRights[moveNum][1] = initialRights[1];
3614         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3615         r = castlingRights[moveNum][3] = initialRights[3];
3616         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3617         r = castlingRights[moveNum][4] = initialRights[4];
3618         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3619         /* wildcastle kludge: always assume King has rights */
3620         r = castlingRights[moveNum][2] = initialRights[2];
3621         r = castlingRights[moveNum][5] = initialRights[5];
3622     }
3623     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3624     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3625
3626     
3627     if (ics_getting_history == H_GOT_REQ_HEADER ||
3628         ics_getting_history == H_GOT_UNREQ_HEADER) {
3629         /* This was an initial position from a move list, not
3630            the current position */
3631         return;
3632     }
3633     
3634     /* Update currentMove and known move number limits */
3635     newMove = newGame || moveNum > forwardMostMove;
3636
3637     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3638     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3639         takeback = forwardMostMove - moveNum;
3640         for (i = 0; i < takeback; i++) {
3641              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3642              SendToProgram("undo\n", &first);
3643         }
3644     }
3645
3646     if (newGame) {
3647         forwardMostMove = backwardMostMove = currentMove = moveNum;
3648         if (gameMode == IcsExamining && moveNum == 0) {
3649           /* Workaround for ICS limitation: we are not told the wild
3650              type when starting to examine a game.  But if we ask for
3651              the move list, the move list header will tell us */
3652             ics_getting_history = H_REQUESTED;
3653             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3654             SendToICS(str);
3655         }
3656     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3657                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3658         forwardMostMove = moveNum;
3659         if (!pausing || currentMove > forwardMostMove)
3660           currentMove = forwardMostMove;
3661     } else {
3662         /* New part of history that is not contiguous with old part */ 
3663         if (pausing && gameMode == IcsExamining) {
3664             pauseExamInvalid = TRUE;
3665             forwardMostMove = pauseExamForwardMostMove;
3666             return;
3667         }
3668         forwardMostMove = backwardMostMove = currentMove = moveNum;
3669         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3670             ics_getting_history = H_REQUESTED;
3671             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3672             SendToICS(str);
3673         }
3674     }
3675     
3676     /* Update the clocks */
3677     if (strchr(elapsed_time, '.')) {
3678       /* Time is in ms */
3679       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3680       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3681     } else {
3682       /* Time is in seconds */
3683       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3684       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3685     }
3686       
3687
3688 #if ZIPPY
3689     if (appData.zippyPlay && newGame &&
3690         gameMode != IcsObserving && gameMode != IcsIdle &&
3691         gameMode != IcsExamining)
3692       ZippyFirstBoard(moveNum, basetime, increment);
3693 #endif
3694     
3695     /* Put the move on the move list, first converting
3696        to canonical algebraic form. */
3697     if (moveNum > 0) {
3698   if (appData.debugMode) {
3699     if (appData.debugMode) { int f = forwardMostMove;
3700         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3701                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3702     }
3703     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3704     fprintf(debugFP, "moveNum = %d\n", moveNum);
3705     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3706     setbuf(debugFP, NULL);
3707   }
3708         if (moveNum <= backwardMostMove) {
3709             /* We don't know what the board looked like before
3710                this move.  Punt. */
3711             strcpy(parseList[moveNum - 1], move_str);
3712             strcat(parseList[moveNum - 1], " ");
3713             strcat(parseList[moveNum - 1], elapsed_time);
3714             moveList[moveNum - 1][0] = NULLCHAR;
3715         } else if (strcmp(move_str, "none") == 0) {
3716             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3717             /* Again, we don't know what the board looked like;
3718                this is really the start of the game. */
3719             parseList[moveNum - 1][0] = NULLCHAR;
3720             moveList[moveNum - 1][0] = NULLCHAR;
3721             backwardMostMove = moveNum;
3722             startedFromSetupPosition = TRUE;
3723             fromX = fromY = toX = toY = -1;
3724         } else {
3725           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3726           //                 So we parse the long-algebraic move string in stead of the SAN move
3727           int valid; char buf[MSG_SIZ], *prom;
3728
3729           // str looks something like "Q/a1-a2"; kill the slash
3730           if(str[1] == '/') 
3731                 sprintf(buf, "%c%s", str[0], str+2);
3732           else  strcpy(buf, str); // might be castling
3733           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3734                 strcat(buf, prom); // long move lacks promo specification!
3735           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3736                 if(appData.debugMode) 
3737                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3738                 strcpy(move_str, buf);
3739           }
3740           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3741                                 &fromX, &fromY, &toX, &toY, &promoChar)
3742                || ParseOneMove(buf, moveNum - 1, &moveType,
3743                                 &fromX, &fromY, &toX, &toY, &promoChar);
3744           // end of long SAN patch
3745           if (valid) {
3746             (void) CoordsToAlgebraic(boards[moveNum - 1],
3747                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3748                                      fromY, fromX, toY, toX, promoChar,
3749                                      parseList[moveNum-1]);
3750             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3751                              castlingRights[moveNum]) ) {
3752               case MT_NONE:
3753               case MT_STALEMATE:
3754               default:
3755                 break;
3756               case MT_CHECK:
3757                 if(gameInfo.variant != VariantShogi)
3758                     strcat(parseList[moveNum - 1], "+");
3759                 break;
3760               case MT_CHECKMATE:
3761               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3762                 strcat(parseList[moveNum - 1], "#");
3763                 break;
3764             }
3765             strcat(parseList[moveNum - 1], " ");
3766             strcat(parseList[moveNum - 1], elapsed_time);
3767             /* currentMoveString is set as a side-effect of ParseOneMove */
3768             strcpy(moveList[moveNum - 1], currentMoveString);
3769             strcat(moveList[moveNum - 1], "\n");
3770           } else {
3771             /* Move from ICS was illegal!?  Punt. */
3772   if (appData.debugMode) {
3773     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3774     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3775   }
3776             strcpy(parseList[moveNum - 1], move_str);
3777             strcat(parseList[moveNum - 1], " ");
3778             strcat(parseList[moveNum - 1], elapsed_time);
3779             moveList[moveNum - 1][0] = NULLCHAR;
3780             fromX = fromY = toX = toY = -1;
3781           }
3782         }
3783   if (appData.debugMode) {
3784     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3785     setbuf(debugFP, NULL);
3786   }
3787
3788 #if ZIPPY
3789         /* Send move to chess program (BEFORE animating it). */
3790         if (appData.zippyPlay && !newGame && newMove && 
3791            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3792
3793             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3794                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3795                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3796                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3797                             move_str);
3798                     DisplayError(str, 0);
3799                 } else {
3800                     if (first.sendTime) {
3801                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3802                     }
3803                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3804                     if (firstMove && !bookHit) {
3805                         firstMove = FALSE;
3806                         if (first.useColors) {
3807                           SendToProgram(gameMode == IcsPlayingWhite ?
3808                                         "white\ngo\n" :
3809                                         "black\ngo\n", &first);
3810                         } else {
3811                           SendToProgram("go\n", &first);
3812                         }
3813                         first.maybeThinking = TRUE;
3814                     }
3815                 }
3816             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3817               if (moveList[moveNum - 1][0] == NULLCHAR) {
3818                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3819                 DisplayError(str, 0);
3820               } else {
3821                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3822                 SendMoveToProgram(moveNum - 1, &first);
3823               }
3824             }
3825         }
3826 #endif
3827     }
3828
3829     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3830         /* If move comes from a remote source, animate it.  If it
3831            isn't remote, it will have already been animated. */
3832         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3833             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3834         }
3835         if (!pausing && appData.highlightLastMove) {
3836             SetHighlights(fromX, fromY, toX, toY);
3837         }
3838     }
3839     
3840     /* Start the clocks */
3841     whiteFlag = blackFlag = FALSE;
3842     appData.clockMode = !(basetime == 0 && increment == 0);
3843     if (ticking == 0) {
3844       ics_clock_paused = TRUE;
3845       StopClocks();
3846     } else if (ticking == 1) {
3847       ics_clock_paused = FALSE;
3848     }
3849     if (gameMode == IcsIdle ||
3850         relation == RELATION_OBSERVING_STATIC ||
3851         relation == RELATION_EXAMINING ||
3852         ics_clock_paused)
3853       DisplayBothClocks();
3854     else
3855       StartClocks();
3856     
3857     /* Display opponents and material strengths */
3858     if (gameInfo.variant != VariantBughouse &&
3859         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3860         if (tinyLayout || smallLayout) {
3861             if(gameInfo.variant == VariantNormal)
3862                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3863                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3864                     basetime, increment);
3865             else
3866                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3867                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3868                     basetime, increment, (int) gameInfo.variant);
3869         } else {
3870             if(gameInfo.variant == VariantNormal)
3871                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3872                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3873                     basetime, increment);
3874             else
3875                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3876                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3877                     basetime, increment, VariantName(gameInfo.variant));
3878         }
3879         DisplayTitle(str);
3880   if (appData.debugMode) {
3881     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3882   }
3883     }
3884
3885    
3886     /* Display the board */
3887     if (!pausing && !appData.noGUI) {
3888       
3889       if (appData.premove)
3890           if (!gotPremove || 
3891              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3892              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3893               ClearPremoveHighlights();
3894
3895       DrawPosition(FALSE, boards[currentMove]);
3896       DisplayMove(moveNum - 1);
3897       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3898             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3899               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3900         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3901       }
3902     }
3903
3904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3905 #if ZIPPY
3906     if(bookHit) { // [HGM] book: simulate book reply
3907         static char bookMove[MSG_SIZ]; // a bit generous?
3908
3909         programStats.nodes = programStats.depth = programStats.time = 
3910         programStats.score = programStats.got_only_move = 0;
3911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3912
3913         strcpy(bookMove, "move ");
3914         strcat(bookMove, bookHit);
3915         HandleMachineMove(bookMove, &first);
3916     }
3917 #endif
3918 }
3919
3920 void
3921 GetMoveListEvent()
3922 {
3923     char buf[MSG_SIZ];
3924     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3925         ics_getting_history = H_REQUESTED;
3926         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3927         SendToICS(buf);
3928     }
3929 }
3930
3931 void
3932 AnalysisPeriodicEvent(force)
3933      int force;
3934 {
3935     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3936          && !force) || !appData.periodicUpdates)
3937       return;
3938
3939     /* Send . command to Crafty to collect stats */
3940     SendToProgram(".\n", &first);
3941
3942     /* Don't send another until we get a response (this makes
3943        us stop sending to old Crafty's which don't understand
3944        the "." command (sending illegal cmds resets node count & time,
3945        which looks bad)) */
3946     programStats.ok_to_send = 0;
3947 }
3948
3949 void ics_update_width(new_width)
3950         int new_width;
3951 {
3952         ics_printf("set width %d\n", new_width);
3953 }
3954
3955 void
3956 SendMoveToProgram(moveNum, cps)
3957      int moveNum;
3958      ChessProgramState *cps;
3959 {
3960     char buf[MSG_SIZ];
3961
3962     if (cps->useUsermove) {
3963       SendToProgram("usermove ", cps);
3964     }
3965     if (cps->useSAN) {
3966       char *space;
3967       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3968         int len = space - parseList[moveNum];
3969         memcpy(buf, parseList[moveNum], len);
3970         buf[len++] = '\n';
3971         buf[len] = NULLCHAR;
3972       } else {
3973         sprintf(buf, "%s\n", parseList[moveNum]);
3974       }
3975       SendToProgram(buf, cps);
3976     } else {
3977       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3978         AlphaRank(moveList[moveNum], 4);
3979         SendToProgram(moveList[moveNum], cps);
3980         AlphaRank(moveList[moveNum], 4); // and back
3981       } else
3982       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3983        * the engine. It would be nice to have a better way to identify castle 
3984        * moves here. */
3985       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3986                                                                          && cps->useOOCastle) {
3987         int fromX = moveList[moveNum][0] - AAA; 
3988         int fromY = moveList[moveNum][1] - ONE;
3989         int toX = moveList[moveNum][2] - AAA; 
3990         int toY = moveList[moveNum][3] - ONE;
3991         if((boards[moveNum][fromY][fromX] == WhiteKing 
3992             && boards[moveNum][toY][toX] == WhiteRook)
3993            || (boards[moveNum][fromY][fromX] == BlackKing 
3994                && boards[moveNum][toY][toX] == BlackRook)) {
3995           if(toX > fromX) SendToProgram("O-O\n", cps);
3996           else SendToProgram("O-O-O\n", cps);
3997         }
3998         else SendToProgram(moveList[moveNum], cps);
3999       }
4000       else SendToProgram(moveList[moveNum], cps);
4001       /* End of additions by Tord */
4002     }
4003
4004     /* [HGM] setting up the opening has brought engine in force mode! */
4005     /*       Send 'go' if we are in a mode where machine should play. */
4006     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4007         (gameMode == TwoMachinesPlay   ||
4008 #ifdef ZIPPY
4009          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4010 #endif
4011          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4012         SendToProgram("go\n", cps);
4013   if (appData.debugMode) {
4014     fprintf(debugFP, "(extra)\n");
4015   }
4016     }
4017     setboardSpoiledMachineBlack = 0;
4018 }
4019
4020 void
4021 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4022      ChessMove moveType;
4023      int fromX, fromY, toX, toY;
4024 {
4025     char user_move[MSG_SIZ];
4026
4027     switch (moveType) {
4028       default:
4029         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4030                 (int)moveType, fromX, fromY, toX, toY);
4031         DisplayError(user_move + strlen("say "), 0);
4032         break;
4033       case WhiteKingSideCastle:
4034       case BlackKingSideCastle:
4035       case WhiteQueenSideCastleWild:
4036       case BlackQueenSideCastleWild:
4037       /* PUSH Fabien */
4038       case WhiteHSideCastleFR:
4039       case BlackHSideCastleFR:
4040       /* POP Fabien */
4041         sprintf(user_move, "o-o\n");
4042         break;
4043       case WhiteQueenSideCastle:
4044       case BlackQueenSideCastle:
4045       case WhiteKingSideCastleWild:
4046       case BlackKingSideCastleWild:
4047       /* PUSH Fabien */
4048       case WhiteASideCastleFR:
4049       case BlackASideCastleFR:
4050       /* POP Fabien */
4051         sprintf(user_move, "o-o-o\n");
4052         break;
4053       case WhitePromotionQueen:
4054       case BlackPromotionQueen:
4055       case WhitePromotionRook:
4056       case BlackPromotionRook:
4057       case WhitePromotionBishop:
4058       case BlackPromotionBishop:
4059       case WhitePromotionKnight:
4060       case BlackPromotionKnight:
4061       case WhitePromotionKing:
4062       case BlackPromotionKing:
4063       case WhitePromotionChancellor:
4064       case BlackPromotionChancellor:
4065       case WhitePromotionArchbishop:
4066       case BlackPromotionArchbishop:
4067         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4068             sprintf(user_move, "%c%c%c%c=%c\n",
4069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4070                 PieceToChar(WhiteFerz));
4071         else if(gameInfo.variant == VariantGreat)
4072             sprintf(user_move, "%c%c%c%c=%c\n",
4073                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4074                 PieceToChar(WhiteMan));
4075         else
4076             sprintf(user_move, "%c%c%c%c=%c\n",
4077                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4078                 PieceToChar(PromoPiece(moveType)));
4079         break;
4080       case WhiteDrop:
4081       case BlackDrop:
4082         sprintf(user_move, "%c@%c%c\n",
4083                 ToUpper(PieceToChar((ChessSquare) fromX)),
4084                 AAA + toX, ONE + toY);
4085         break;
4086       case NormalMove:
4087       case WhiteCapturesEnPassant:
4088       case BlackCapturesEnPassant:
4089       case IllegalMove:  /* could be a variant we don't quite understand */
4090         sprintf(user_move, "%c%c%c%c\n",
4091                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4092         break;
4093     }
4094     SendToICS(user_move);
4095     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4096         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4097 }
4098
4099 void
4100 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4101      int rf, ff, rt, ft;
4102      char promoChar;
4103      char move[7];
4104 {
4105     if (rf == DROP_RANK) {
4106         sprintf(move, "%c@%c%c\n",
4107                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4108     } else {
4109         if (promoChar == 'x' || promoChar == NULLCHAR) {
4110             sprintf(move, "%c%c%c%c\n",
4111                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4112         } else {
4113             sprintf(move, "%c%c%c%c%c\n",
4114                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4115         }
4116     }
4117 }
4118
4119 void
4120 ProcessICSInitScript(f)
4121      FILE *f;
4122 {
4123     char buf[MSG_SIZ];
4124
4125     while (fgets(buf, MSG_SIZ, f)) {
4126         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4127     }
4128
4129     fclose(f);
4130 }
4131
4132
4133 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4134 void
4135 AlphaRank(char *move, int n)
4136 {
4137 //    char *p = move, c; int x, y;
4138
4139     if (appData.debugMode) {
4140         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4141     }
4142
4143     if(move[1]=='*' && 
4144        move[2]>='0' && move[2]<='9' &&
4145        move[3]>='a' && move[3]<='x'    ) {
4146         move[1] = '@';
4147         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4148         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4149     } else
4150     if(move[0]>='0' && move[0]<='9' &&
4151        move[1]>='a' && move[1]<='x' &&
4152        move[2]>='0' && move[2]<='9' &&
4153        move[3]>='a' && move[3]<='x'    ) {
4154         /* input move, Shogi -> normal */
4155         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4156         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4157         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4158         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4159     } else
4160     if(move[1]=='@' &&
4161        move[3]>='0' && move[3]<='9' &&
4162        move[2]>='a' && move[2]<='x'    ) {
4163         move[1] = '*';
4164         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4165         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4166     } else
4167     if(
4168        move[0]>='a' && move[0]<='x' &&
4169        move[3]>='0' && move[3]<='9' &&
4170        move[2]>='a' && move[2]<='x'    ) {
4171          /* output move, normal -> Shogi */
4172         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4173         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4174         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4175         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4176         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4177     }
4178     if (appData.debugMode) {
4179         fprintf(debugFP, "   out = '%s'\n", move);
4180     }
4181 }
4182
4183 /* Parser for moves from gnuchess, ICS, or user typein box */
4184 Boolean
4185 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4186      char *move;
4187      int moveNum;
4188      ChessMove *moveType;
4189      int *fromX, *fromY, *toX, *toY;
4190      char *promoChar;
4191 {       
4192     if (appData.debugMode) {
4193         fprintf(debugFP, "move to parse: %s\n", move);
4194     }
4195     *moveType = yylexstr(moveNum, move);
4196
4197     switch (*moveType) {
4198       case WhitePromotionChancellor:
4199       case BlackPromotionChancellor:
4200       case WhitePromotionArchbishop:
4201       case BlackPromotionArchbishop:
4202       case WhitePromotionQueen:
4203       case BlackPromotionQueen:
4204       case WhitePromotionRook:
4205       case BlackPromotionRook:
4206       case WhitePromotionBishop:
4207       case BlackPromotionBishop:
4208       case WhitePromotionKnight:
4209       case BlackPromotionKnight:
4210       case WhitePromotionKing:
4211       case BlackPromotionKing:
4212       case NormalMove:
4213       case WhiteCapturesEnPassant:
4214       case BlackCapturesEnPassant:
4215       case WhiteKingSideCastle:
4216       case WhiteQueenSideCastle:
4217       case BlackKingSideCastle:
4218       case BlackQueenSideCastle:
4219       case WhiteKingSideCastleWild:
4220       case WhiteQueenSideCastleWild:
4221       case BlackKingSideCastleWild:
4222       case BlackQueenSideCastleWild:
4223       /* Code added by Tord: */
4224       case WhiteHSideCastleFR:
4225       case WhiteASideCastleFR:
4226       case BlackHSideCastleFR:
4227       case BlackASideCastleFR:
4228       /* End of code added by Tord */
4229       case IllegalMove:         /* bug or odd chess variant */
4230         *fromX = currentMoveString[0] - AAA;
4231         *fromY = currentMoveString[1] - ONE;
4232         *toX = currentMoveString[2] - AAA;
4233         *toY = currentMoveString[3] - ONE;
4234         *promoChar = currentMoveString[4];
4235         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4236             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4237     if (appData.debugMode) {
4238         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4239     }
4240             *fromX = *fromY = *toX = *toY = 0;
4241             return FALSE;
4242         }
4243         if (appData.testLegality) {
4244           return (*moveType != IllegalMove);
4245         } else {
4246           return !(fromX == fromY && toX == toY);
4247         }
4248
4249       case WhiteDrop:
4250       case BlackDrop:
4251         *fromX = *moveType == WhiteDrop ?
4252           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4253           (int) CharToPiece(ToLower(currentMoveString[0]));
4254         *fromY = DROP_RANK;
4255         *toX = currentMoveString[2] - AAA;
4256         *toY = currentMoveString[3] - ONE;
4257         *promoChar = NULLCHAR;
4258         return TRUE;
4259
4260       case AmbiguousMove:
4261       case ImpossibleMove:
4262       case (ChessMove) 0:       /* end of file */
4263       case ElapsedTime:
4264       case Comment:
4265       case PGNTag:
4266       case NAG:
4267       case WhiteWins:
4268       case BlackWins:
4269       case GameIsDrawn:
4270       default:
4271     if (appData.debugMode) {
4272         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4273     }
4274         /* bug? */
4275         *fromX = *fromY = *toX = *toY = 0;
4276         *promoChar = NULLCHAR;
4277         return FALSE;
4278     }
4279 }
4280
4281 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4282 // All positions will have equal probability, but the current method will not provide a unique
4283 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4284 #define DARK 1
4285 #define LITE 2
4286 #define ANY 3
4287
4288 int squaresLeft[4];
4289 int piecesLeft[(int)BlackPawn];
4290 int seed, nrOfShuffles;
4291
4292 void GetPositionNumber()
4293 {       // sets global variable seed
4294         int i;
4295
4296         seed = appData.defaultFrcPosition;
4297         if(seed < 0) { // randomize based on time for negative FRC position numbers
4298                 for(i=0; i<50; i++) seed += random();
4299                 seed = random() ^ random() >> 8 ^ random() << 8;
4300                 if(seed<0) seed = -seed;
4301         }
4302 }
4303
4304 int put(Board board, int pieceType, int rank, int n, int shade)
4305 // put the piece on the (n-1)-th empty squares of the given shade
4306 {
4307         int i;
4308
4309         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4310                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4311                         board[rank][i] = (ChessSquare) pieceType;
4312                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4313                         squaresLeft[ANY]--;
4314                         piecesLeft[pieceType]--; 
4315                         return i;
4316                 }
4317         }
4318         return -1;
4319 }
4320
4321
4322 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4323 // calculate where the next piece goes, (any empty square), and put it there
4324 {
4325         int i;
4326
4327         i = seed % squaresLeft[shade];
4328         nrOfShuffles *= squaresLeft[shade];
4329         seed /= squaresLeft[shade];
4330         put(board, pieceType, rank, i, shade);
4331 }
4332
4333 void AddTwoPieces(Board board, int pieceType, int rank)
4334 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4335 {
4336         int i, n=squaresLeft[ANY], j=n-1, k;
4337
4338         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4339         i = seed % k;  // pick one
4340         nrOfShuffles *= k;
4341         seed /= k;
4342         while(i >= j) i -= j--;
4343         j = n - 1 - j; i += j;
4344         put(board, pieceType, rank, j, ANY);
4345         put(board, pieceType, rank, i, ANY);
4346 }
4347
4348 void SetUpShuffle(Board board, int number)
4349 {
4350         int i, p, first=1;
4351
4352         GetPositionNumber(); nrOfShuffles = 1;
4353
4354         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4355         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4356         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4357
4358         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4359
4360         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4361             p = (int) board[0][i];
4362             if(p < (int) BlackPawn) piecesLeft[p] ++;
4363             board[0][i] = EmptySquare;
4364         }
4365
4366         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4367             // shuffles restricted to allow normal castling put KRR first
4368             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4369                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4370             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4371                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4372             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4373                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4374             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4375                 put(board, WhiteRook, 0, 0, ANY);
4376             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4377         }
4378
4379         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4380             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4381             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4382                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4383                 while(piecesLeft[p] >= 2) {
4384                     AddOnePiece(board, p, 0, LITE);
4385                     AddOnePiece(board, p, 0, DARK);
4386                 }
4387                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4388             }
4389
4390         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4391             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4392             // but we leave King and Rooks for last, to possibly obey FRC restriction
4393             if(p == (int)WhiteRook) continue;
4394             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4395             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4396         }
4397
4398         // now everything is placed, except perhaps King (Unicorn) and Rooks
4399
4400         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4401             // Last King gets castling rights
4402             while(piecesLeft[(int)WhiteUnicorn]) {
4403                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4404                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4405             }
4406
4407             while(piecesLeft[(int)WhiteKing]) {
4408                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4409                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4410             }
4411
4412
4413         } else {
4414             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4415             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4416         }
4417
4418         // Only Rooks can be left; simply place them all
4419         while(piecesLeft[(int)WhiteRook]) {
4420                 i = put(board, WhiteRook, 0, 0, ANY);
4421                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4422                         if(first) {
4423                                 first=0;
4424                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4425                         }
4426                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4427                 }
4428         }
4429         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4430             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4431         }
4432
4433         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4434 }
4435
4436 int SetCharTable( char *table, const char * map )
4437 /* [HGM] moved here from winboard.c because of its general usefulness */
4438 /*       Basically a safe strcpy that uses the last character as King */
4439 {
4440     int result = FALSE; int NrPieces;
4441
4442     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4443                     && NrPieces >= 12 && !(NrPieces&1)) {
4444         int i; /* [HGM] Accept even length from 12 to 34 */
4445
4446         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4447         for( i=0; i<NrPieces/2-1; i++ ) {
4448             table[i] = map[i];
4449             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4450         }
4451         table[(int) WhiteKing]  = map[NrPieces/2-1];
4452         table[(int) BlackKing]  = map[NrPieces-1];
4453
4454         result = TRUE;
4455     }
4456
4457     return result;
4458 }
4459
4460 void Prelude(Board board)
4461 {       // [HGM] superchess: random selection of exo-pieces
4462         int i, j, k; ChessSquare p; 
4463         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4464
4465         GetPositionNumber(); // use FRC position number
4466
4467         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4468             SetCharTable(pieceToChar, appData.pieceToCharTable);
4469             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4470                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4471         }
4472
4473         j = seed%4;                 seed /= 4; 
4474         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4475         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4476         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4477         j = seed%3 + (seed%3 >= j); seed /= 3; 
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; 
4482         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2; 
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4490         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4491         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4492         put(board, exoPieces[0],    0, 0, ANY);
4493         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4494 }
4495
4496 void
4497 InitPosition(redraw)
4498      int redraw;
4499 {
4500     ChessSquare (* pieces)[BOARD_SIZE];
4501     int i, j, pawnRow, overrule,
4502     oldx = gameInfo.boardWidth,
4503     oldy = gameInfo.boardHeight,
4504     oldh = gameInfo.holdingsWidth,
4505     oldv = gameInfo.variant;
4506
4507     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4508
4509     /* [AS] Initialize pv info list [HGM] and game status */
4510     {
4511         for( i=0; i<MAX_MOVES; i++ ) {
4512             pvInfoList[i].depth = 0;
4513             epStatus[i]=EP_NONE;
4514             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4515         }
4516
4517         initialRulePlies = 0; /* 50-move counter start */
4518
4519         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4520         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4521     }
4522
4523     
4524     /* [HGM] logic here is completely changed. In stead of full positions */
4525     /* the initialized data only consist of the two backranks. The switch */
4526     /* selects which one we will use, which is than copied to the Board   */
4527     /* initialPosition, which for the rest is initialized by Pawns and    */
4528     /* empty squares. This initial position is then copied to boards[0],  */
4529     /* possibly after shuffling, so that it remains available.            */
4530
4531     gameInfo.holdingsWidth = 0; /* default board sizes */
4532     gameInfo.boardWidth    = 8;
4533     gameInfo.boardHeight   = 8;
4534     gameInfo.holdingsSize  = 0;
4535     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4536     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4537     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4538
4539     switch (gameInfo.variant) {
4540     case VariantFischeRandom:
4541       shuffleOpenings = TRUE;
4542     default:
4543       pieces = FIDEArray;
4544       break;
4545     case VariantShatranj:
4546       pieces = ShatranjArray;
4547       nrCastlingRights = 0;
4548       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4549       break;
4550     case VariantTwoKings:
4551       pieces = twoKingsArray;
4552       break;
4553     case VariantCapaRandom:
4554       shuffleOpenings = TRUE;
4555     case VariantCapablanca:
4556       pieces = CapablancaArray;
4557       gameInfo.boardWidth = 10;
4558       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4559       break;
4560     case VariantGothic:
4561       pieces = GothicArray;
4562       gameInfo.boardWidth = 10;
4563       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4564       break;
4565     case VariantJanus:
4566       pieces = JanusArray;
4567       gameInfo.boardWidth = 10;
4568       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4569       nrCastlingRights = 6;
4570         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4571         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4572         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4573         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4574         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4575         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4576       break;
4577     case VariantFalcon:
4578       pieces = FalconArray;
4579       gameInfo.boardWidth = 10;
4580       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4581       break;
4582     case VariantXiangqi:
4583       pieces = XiangqiArray;
4584       gameInfo.boardWidth  = 9;
4585       gameInfo.boardHeight = 10;
4586       nrCastlingRights = 0;
4587       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4588       break;
4589     case VariantShogi:
4590       pieces = ShogiArray;
4591       gameInfo.boardWidth  = 9;
4592       gameInfo.boardHeight = 9;
4593       gameInfo.holdingsSize = 7;
4594       nrCastlingRights = 0;
4595       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4596       break;
4597     case VariantCourier:
4598       pieces = CourierArray;
4599       gameInfo.boardWidth  = 12;
4600       nrCastlingRights = 0;
4601       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4602       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4603       break;
4604     case VariantKnightmate:
4605       pieces = KnightmateArray;
4606       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4607       break;
4608     case VariantFairy:
4609       pieces = fairyArray;
4610       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4611       break;
4612     case VariantGreat:
4613       pieces = GreatArray;
4614       gameInfo.boardWidth = 10;
4615       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4616       gameInfo.holdingsSize = 8;
4617       break;
4618     case VariantSuper:
4619       pieces = FIDEArray;
4620       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4621       gameInfo.holdingsSize = 8;
4622       startedFromSetupPosition = TRUE;
4623       break;
4624     case VariantCrazyhouse:
4625     case VariantBughouse:
4626       pieces = FIDEArray;
4627       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4628       gameInfo.holdingsSize = 5;
4629       break;
4630     case VariantWildCastle:
4631       pieces = FIDEArray;
4632       /* !!?shuffle with kings guaranteed to be on d or e file */
4633       shuffleOpenings = 1;
4634       break;
4635     case VariantNoCastle:
4636       pieces = FIDEArray;
4637       nrCastlingRights = 0;
4638       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4639       /* !!?unconstrained back-rank shuffle */
4640       shuffleOpenings = 1;
4641       break;
4642     }
4643
4644     overrule = 0;
4645     if(appData.NrFiles >= 0) {
4646         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4647         gameInfo.boardWidth = appData.NrFiles;
4648     }
4649     if(appData.NrRanks >= 0) {
4650         gameInfo.boardHeight = appData.NrRanks;
4651     }
4652     if(appData.holdingsSize >= 0) {
4653         i = appData.holdingsSize;
4654         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4655         gameInfo.holdingsSize = i;
4656     }
4657     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4658     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4659         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4660
4661     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4662     if(pawnRow < 1) pawnRow = 1;
4663
4664     /* User pieceToChar list overrules defaults */
4665     if(appData.pieceToCharTable != NULL)
4666         SetCharTable(pieceToChar, appData.pieceToCharTable);
4667
4668     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4669
4670         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4671             s = (ChessSquare) 0; /* account holding counts in guard band */
4672         for( i=0; i<BOARD_HEIGHT; i++ )
4673             initialPosition[i][j] = s;
4674
4675         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4676         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4677         initialPosition[pawnRow][j] = WhitePawn;
4678         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4679         if(gameInfo.variant == VariantXiangqi) {
4680             if(j&1) {
4681                 initialPosition[pawnRow][j] = 
4682                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4683                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4684                    initialPosition[2][j] = WhiteCannon;
4685                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4686                 }
4687             }
4688         }
4689         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4690     }
4691     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4692
4693             j=BOARD_LEFT+1;
4694             initialPosition[1][j] = WhiteBishop;
4695             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4696             j=BOARD_RGHT-2;
4697             initialPosition[1][j] = WhiteRook;
4698             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4699     }
4700
4701     if( nrCastlingRights == -1) {
4702         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4703         /*       This sets default castling rights from none to normal corners   */
4704         /* Variants with other castling rights must set them themselves above    */
4705         nrCastlingRights = 6;
4706        
4707         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4708         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4709         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4710         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4711         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4712         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4713      }
4714
4715      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4716      if(gameInfo.variant == VariantGreat) { // promotion commoners
4717         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4718         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4719         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4720         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4721      }
4722   if (appData.debugMode) {
4723     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4724   }
4725     if(shuffleOpenings) {
4726         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4727         startedFromSetupPosition = TRUE;
4728     }
4729     if(startedFromPositionFile) {
4730       /* [HGM] loadPos: use PositionFile for every new game */
4731       CopyBoard(initialPosition, filePosition);
4732       for(i=0; i<nrCastlingRights; i++)
4733           castlingRights[0][i] = initialRights[i] = fileRights[i];
4734       startedFromSetupPosition = TRUE;
4735     }
4736
4737     CopyBoard(boards[0], initialPosition);
4738
4739     if(oldx != gameInfo.boardWidth ||
4740        oldy != gameInfo.boardHeight ||
4741        oldh != gameInfo.holdingsWidth
4742 #ifdef GOTHIC
4743        || oldv == VariantGothic ||        // For licensing popups
4744        gameInfo.variant == VariantGothic
4745 #endif
4746 #ifdef FALCON
4747        || oldv == VariantFalcon ||
4748        gameInfo.variant == VariantFalcon
4749 #endif
4750                                          )
4751             InitDrawingSizes(-2 ,0);
4752
4753     if (redraw)
4754       DrawPosition(TRUE, boards[currentMove]);
4755 }
4756
4757 void
4758 SendBoard(cps, moveNum)
4759      ChessProgramState *cps;
4760      int moveNum;
4761 {
4762     char message[MSG_SIZ];
4763     
4764     if (cps->useSetboard) {
4765       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4766       sprintf(message, "setboard %s\n", fen);
4767       SendToProgram(message, cps);
4768       free(fen);
4769
4770     } else {
4771       ChessSquare *bp;
4772       int i, j;
4773       /* Kludge to set black to move, avoiding the troublesome and now
4774        * deprecated "black" command.
4775        */
4776       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4777
4778       SendToProgram("edit\n", cps);
4779       SendToProgram("#\n", cps);
4780       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4781         bp = &boards[moveNum][i][BOARD_LEFT];
4782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4783           if ((int) *bp < (int) BlackPawn) {
4784             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4785                     AAA + j, ONE + i);
4786             if(message[0] == '+' || message[0] == '~') {
4787                 sprintf(message, "%c%c%c+\n",
4788                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4789                         AAA + j, ONE + i);
4790             }
4791             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4792                 message[1] = BOARD_RGHT   - 1 - j + '1';
4793                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4794             }
4795             SendToProgram(message, cps);
4796           }
4797         }
4798       }
4799     
4800       SendToProgram("c\n", cps);
4801       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4802         bp = &boards[moveNum][i][BOARD_LEFT];
4803         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4804           if (((int) *bp != (int) EmptySquare)
4805               && ((int) *bp >= (int) BlackPawn)) {
4806             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4807                     AAA + j, ONE + i);
4808             if(message[0] == '+' || message[0] == '~') {
4809                 sprintf(message, "%c%c%c+\n",
4810                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4811                         AAA + j, ONE + i);
4812             }
4813             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4814                 message[1] = BOARD_RGHT   - 1 - j + '1';
4815                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4816             }
4817             SendToProgram(message, cps);
4818           }
4819         }
4820       }
4821     
4822       SendToProgram(".\n", cps);
4823     }
4824     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4825 }
4826
4827 int
4828 IsPromotion(fromX, fromY, toX, toY)
4829      int fromX, fromY, toX, toY;
4830 {
4831     /* [HGM] add Shogi promotions */
4832     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4833     ChessSquare piece;
4834
4835     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4836       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4837    /* [HGM] Note to self: line above also weeds out drops */
4838     piece = boards[currentMove][fromY][fromX];
4839     if(gameInfo.variant == VariantShogi) {
4840         promotionZoneSize = 3;
4841         highestPromotingPiece = (int)WhiteKing;
4842         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4843            and if in normal chess we then allow promotion to King, why not
4844            allow promotion of other piece in Shogi?                         */
4845     }
4846     if((int)piece >= BlackPawn) {
4847         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4848              return FALSE;
4849         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4850     } else {
4851         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4852            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4853     }
4854     return ( (int)piece <= highestPromotingPiece );
4855 }
4856
4857 int
4858 InPalace(row, column)
4859      int row, column;
4860 {   /* [HGM] for Xiangqi */
4861     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4862          column < (BOARD_WIDTH + 4)/2 &&
4863          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4864     return FALSE;
4865 }
4866
4867 int
4868 PieceForSquare (x, y)
4869      int x;
4870      int y;
4871 {
4872   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4873      return -1;
4874   else
4875      return boards[currentMove][y][x];
4876 }
4877
4878 int
4879 OKToStartUserMove(x, y)
4880      int x, y;
4881 {
4882     ChessSquare from_piece;
4883     int white_piece;
4884
4885     if (matchMode) return FALSE;
4886     if (gameMode == EditPosition) return TRUE;
4887
4888     if (x >= 0 && y >= 0)
4889       from_piece = boards[currentMove][y][x];
4890     else
4891       from_piece = EmptySquare;
4892
4893     if (from_piece == EmptySquare) return FALSE;
4894
4895     white_piece = (int)from_piece >= (int)WhitePawn &&
4896       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4897
4898     switch (gameMode) {
4899       case PlayFromGameFile:
4900       case AnalyzeFile:
4901       case TwoMachinesPlay:
4902       case EndOfGame:
4903         return FALSE;
4904
4905       case IcsObserving:
4906       case IcsIdle:
4907         return FALSE;
4908
4909       case MachinePlaysWhite:
4910       case IcsPlayingBlack:
4911         if (appData.zippyPlay) return FALSE;
4912         if (white_piece) {
4913             DisplayMoveError(_("You are playing Black"));
4914             return FALSE;
4915         }
4916         break;
4917
4918       case MachinePlaysBlack:
4919       case IcsPlayingWhite:
4920         if (appData.zippyPlay) return FALSE;
4921         if (!white_piece) {
4922             DisplayMoveError(_("You are playing White"));
4923             return FALSE;
4924         }
4925         break;
4926
4927       case EditGame:
4928         if (!white_piece && WhiteOnMove(currentMove)) {
4929             DisplayMoveError(_("It is White's turn"));
4930             return FALSE;
4931         }           
4932         if (white_piece && !WhiteOnMove(currentMove)) {
4933             DisplayMoveError(_("It is Black's turn"));
4934             return FALSE;
4935         }           
4936         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4937             /* Editing correspondence game history */
4938             /* Could disallow this or prompt for confirmation */
4939             cmailOldMove = -1;
4940         }
4941         if (currentMove < forwardMostMove) {
4942             /* Discarding moves */
4943             /* Could prompt for confirmation here,
4944                but I don't think that's such a good idea */
4945             forwardMostMove = currentMove;
4946         }
4947         break;
4948
4949       case BeginningOfGame:
4950         if (appData.icsActive) return FALSE;
4951         if (!appData.noChessProgram) {
4952             if (!white_piece) {
4953                 DisplayMoveError(_("You are playing White"));
4954                 return FALSE;
4955             }
4956         }
4957         break;
4958         
4959       case Training:
4960         if (!white_piece && WhiteOnMove(currentMove)) {
4961             DisplayMoveError(_("It is White's turn"));
4962             return FALSE;
4963         }           
4964         if (white_piece && !WhiteOnMove(currentMove)) {
4965             DisplayMoveError(_("It is Black's turn"));
4966             return FALSE;
4967         }           
4968         break;
4969
4970       default:
4971       case IcsExamining:
4972         break;
4973     }
4974     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4975         && gameMode != AnalyzeFile && gameMode != Training) {
4976         DisplayMoveError(_("Displayed position is not current"));
4977         return FALSE;
4978     }
4979     return TRUE;
4980 }
4981
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4984 int lastLoadGameUseList = FALSE;
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4986 ChessMove lastLoadGameStart = (ChessMove) 0;
4987
4988 ChessMove
4989 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4990      int fromX, fromY, toX, toY;
4991      int promoChar;
4992      Boolean captureOwn;
4993 {
4994     ChessMove moveType;
4995     ChessSquare pdown, pup;
4996
4997     if (fromX < 0 || fromY < 0) return ImpossibleMove;
4998
4999     /* [HGM] suppress all moves into holdings area and guard band */
5000     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5001             return ImpossibleMove;
5002
5003     /* [HGM] <sameColor> moved to here from winboard.c */
5004     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5005     pdown = boards[currentMove][fromY][fromX];
5006     pup = boards[currentMove][toY][toX];
5007     if (    gameMode != EditPosition && !captureOwn &&
5008             (WhitePawn <= pdown && pdown < BlackPawn &&
5009              WhitePawn <= pup && pup < BlackPawn  ||
5010              BlackPawn <= pdown && pdown < EmptySquare &&
5011              BlackPawn <= pup && pup < EmptySquare 
5012             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5013                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5014                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5015                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5016                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5017         )           )
5018          return Comment;
5019
5020     /* Check if the user is playing in turn.  This is complicated because we
5021        let the user "pick up" a piece before it is his turn.  So the piece he
5022        tried to pick up may have been captured by the time he puts it down!
5023        Therefore we use the color the user is supposed to be playing in this
5024        test, not the color of the piece that is currently on the starting
5025        square---except in EditGame mode, where the user is playing both
5026        sides; fortunately there the capture race can't happen.  (It can
5027        now happen in IcsExamining mode, but that's just too bad.  The user
5028        will get a somewhat confusing message in that case.)
5029        */
5030
5031     switch (gameMode) {
5032       case PlayFromGameFile:
5033       case AnalyzeFile:
5034       case TwoMachinesPlay:
5035       case EndOfGame:
5036       case IcsObserving:
5037       case IcsIdle:
5038         /* We switched into a game mode where moves are not accepted,
5039            perhaps while the mouse button was down. */
5040         return ImpossibleMove;
5041
5042       case MachinePlaysWhite:
5043         /* User is moving for Black */
5044         if (WhiteOnMove(currentMove)) {
5045             DisplayMoveError(_("It is White's turn"));
5046             return ImpossibleMove;
5047         }
5048         break;
5049
5050       case MachinePlaysBlack:
5051         /* User is moving for White */
5052         if (!WhiteOnMove(currentMove)) {
5053             DisplayMoveError(_("It is Black's turn"));
5054             return ImpossibleMove;
5055         }
5056         break;
5057
5058       case EditGame:
5059       case IcsExamining:
5060       case BeginningOfGame:
5061       case AnalyzeMode:
5062       case Training:
5063         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5064             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5065             /* User is moving for Black */
5066             if (WhiteOnMove(currentMove)) {
5067                 DisplayMoveError(_("It is White's turn"));
5068                 return ImpossibleMove;
5069             }
5070         } else {
5071             /* User is moving for White */
5072             if (!WhiteOnMove(currentMove)) {
5073                 DisplayMoveError(_("It is Black's turn"));
5074                 return ImpossibleMove;
5075             }
5076         }
5077         break;
5078
5079       case IcsPlayingBlack:
5080         /* User is moving for Black */
5081         if (WhiteOnMove(currentMove)) {
5082             if (!appData.premove) {
5083                 DisplayMoveError(_("It is White's turn"));
5084             } else if (toX >= 0 && toY >= 0) {
5085                 premoveToX = toX;
5086                 premoveToY = toY;
5087                 premoveFromX = fromX;
5088                 premoveFromY = fromY;
5089                 premovePromoChar = promoChar;
5090                 gotPremove = 1;
5091                 if (appData.debugMode) 
5092                     fprintf(debugFP, "Got premove: fromX %d,"
5093                             "fromY %d, toX %d, toY %d\n",
5094                             fromX, fromY, toX, toY);
5095             }
5096             return ImpossibleMove;
5097         }
5098         break;
5099
5100       case IcsPlayingWhite:
5101         /* User is moving for White */
5102         if (!WhiteOnMove(currentMove)) {
5103             if (!appData.premove) {
5104                 DisplayMoveError(_("It is Black's turn"));
5105             } else if (toX >= 0 && toY >= 0) {
5106                 premoveToX = toX;
5107                 premoveToY = toY;
5108                 premoveFromX = fromX;
5109                 premoveFromY = fromY;
5110                 premovePromoChar = promoChar;
5111                 gotPremove = 1;
5112                 if (appData.debugMode) 
5113                     fprintf(debugFP, "Got premove: fromX %d,"
5114                             "fromY %d, toX %d, toY %d\n",
5115                             fromX, fromY, toX, toY);
5116             }
5117             return ImpossibleMove;
5118         }
5119         break;
5120
5121       default:
5122         break;
5123
5124       case EditPosition:
5125         /* EditPosition, empty square, or different color piece;
5126            click-click move is possible */
5127         if (toX == -2 || toY == -2) {
5128             boards[0][fromY][fromX] = EmptySquare;
5129             return AmbiguousMove;
5130         } else if (toX >= 0 && toY >= 0) {
5131             boards[0][toY][toX] = boards[0][fromY][fromX];
5132             boards[0][fromY][fromX] = EmptySquare;
5133             return AmbiguousMove;
5134         }
5135         return ImpossibleMove;
5136     }
5137
5138     /* [HGM] If move started in holdings, it means a drop */
5139     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5140          if( pup != EmptySquare ) return ImpossibleMove;
5141          if(appData.testLegality) {
5142              /* it would be more logical if LegalityTest() also figured out
5143               * which drops are legal. For now we forbid pawns on back rank.
5144               * Shogi is on its own here...
5145               */
5146              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5147                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5148                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5149          }
5150          return WhiteDrop; /* Not needed to specify white or black yet */
5151     }
5152
5153     userOfferedDraw = FALSE;
5154         
5155     /* [HGM] always test for legality, to get promotion info */
5156     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5157                           epStatus[currentMove], castlingRights[currentMove],
5158                                          fromY, fromX, toY, toX, promoChar);
5159     /* [HGM] but possibly ignore an IllegalMove result */
5160     if (appData.testLegality) {
5161         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5162             DisplayMoveError(_("Illegal move"));
5163             return ImpossibleMove;
5164         }
5165     }
5166 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5167     return moveType;
5168     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5169        function is made into one that returns an OK move type if FinishMove
5170        should be called. This to give the calling driver routine the
5171        opportunity to finish the userMove input with a promotion popup,
5172        without bothering the user with this for invalid or illegal moves */
5173
5174 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5175 }
5176
5177 /* Common tail of UserMoveEvent and DropMenuEvent */
5178 int
5179 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5180      ChessMove moveType;
5181      int fromX, fromY, toX, toY;
5182      /*char*/int promoChar;
5183 {
5184     char *bookHit = 0;
5185 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5186     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5187         // [HGM] superchess: suppress promotions to non-available piece
5188         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5189         if(WhiteOnMove(currentMove)) {
5190             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5191         } else {
5192             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5193         }
5194     }
5195
5196     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5197        move type in caller when we know the move is a legal promotion */
5198     if(moveType == NormalMove && promoChar)
5199         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5200 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5201     /* [HGM] convert drag-and-drop piece drops to standard form */
5202     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5203          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5204            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5205                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5206 //         fromX = boards[currentMove][fromY][fromX];
5207            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5208            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5209            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5210            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5211          fromY = DROP_RANK;
5212     }
5213
5214     /* [HGM] <popupFix> The following if has been moved here from
5215        UserMoveEvent(). Because it seemed to belon here (why not allow
5216        piece drops in training games?), and because it can only be
5217        performed after it is known to what we promote. */
5218     if (gameMode == Training) {
5219       /* compare the move played on the board to the next move in the
5220        * game. If they match, display the move and the opponent's response. 
5221        * If they don't match, display an error message.
5222        */
5223       int saveAnimate;
5224       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5225       CopyBoard(testBoard, boards[currentMove]);
5226       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5227
5228       if (CompareBoards(testBoard, boards[currentMove+1])) {
5229         ForwardInner(currentMove+1);
5230
5231         /* Autoplay the opponent's response.
5232          * if appData.animate was TRUE when Training mode was entered,
5233          * the response will be animated.
5234          */
5235         saveAnimate = appData.animate;
5236         appData.animate = animateTraining;
5237         ForwardInner(currentMove+1);
5238         appData.animate = saveAnimate;
5239
5240         /* check for the end of the game */
5241         if (currentMove >= forwardMostMove) {
5242           gameMode = PlayFromGameFile;
5243           ModeHighlight();
5244           SetTrainingModeOff();
5245           DisplayInformation(_("End of game"));
5246         }
5247       } else {
5248         DisplayError(_("Incorrect move"), 0);
5249       }
5250       return 1;
5251     }
5252
5253   /* Ok, now we know that the move is good, so we can kill
5254      the previous line in Analysis Mode */
5255   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5256     forwardMostMove = currentMove;
5257   }
5258
5259   /* If we need the chess program but it's dead, restart it */
5260   ResurrectChessProgram();
5261
5262   /* A user move restarts a paused game*/
5263   if (pausing)
5264     PauseEvent();
5265
5266   thinkOutput[0] = NULLCHAR;
5267
5268   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5269
5270   if (gameMode == BeginningOfGame) {
5271     if (appData.noChessProgram) {
5272       gameMode = EditGame;
5273       SetGameInfo();
5274     } else {
5275       char buf[MSG_SIZ];
5276       gameMode = MachinePlaysBlack;
5277       StartClocks();
5278       SetGameInfo();
5279       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5280       DisplayTitle(buf);
5281       if (first.sendName) {
5282         sprintf(buf, "name %s\n", gameInfo.white);
5283         SendToProgram(buf, &first);
5284       }
5285       StartClocks();
5286     }
5287     ModeHighlight();
5288   }
5289 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5290   /* Relay move to ICS or chess engine */
5291   if (appData.icsActive) {
5292     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5293         gameMode == IcsExamining) {
5294       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5295       ics_user_moved = 1;
5296     }
5297   } else {
5298     if (first.sendTime && (gameMode == BeginningOfGame ||
5299                            gameMode == MachinePlaysWhite ||
5300                            gameMode == MachinePlaysBlack)) {
5301       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5302     }
5303     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5304          // [HGM] book: if program might be playing, let it use book
5305         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5306         first.maybeThinking = TRUE;
5307     } else SendMoveToProgram(forwardMostMove-1, &first);
5308     if (currentMove == cmailOldMove + 1) {
5309       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5310     }
5311   }
5312
5313   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5314
5315   switch (gameMode) {
5316   case EditGame:
5317     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5318                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5319     case MT_NONE:
5320     case MT_CHECK:
5321       break;
5322     case MT_CHECKMATE:
5323     case MT_STAINMATE:
5324       if (WhiteOnMove(currentMove)) {
5325         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5326       } else {
5327         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5328       }
5329       break;
5330     case MT_STALEMATE:
5331       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5332       break;
5333     }
5334     break;
5335     
5336   case MachinePlaysBlack:
5337   case MachinePlaysWhite:
5338     /* disable certain menu options while machine is thinking */
5339     SetMachineThinkingEnables();
5340     break;
5341
5342   default:
5343     break;
5344   }
5345
5346   if(bookHit) { // [HGM] book: simulate book reply
5347         static char bookMove[MSG_SIZ]; // a bit generous?
5348
5349         programStats.nodes = programStats.depth = programStats.time = 
5350         programStats.score = programStats.got_only_move = 0;
5351         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5352
5353         strcpy(bookMove, "move ");
5354         strcat(bookMove, bookHit);
5355         HandleMachineMove(bookMove, &first);
5356   }
5357   return 1;
5358 }
5359
5360 void
5361 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5362      int fromX, fromY, toX, toY;
5363      int promoChar;
5364 {
5365     /* [HGM] This routine was added to allow calling of its two logical
5366        parts from other modules in the old way. Before, UserMoveEvent()
5367        automatically called FinishMove() if the move was OK, and returned
5368        otherwise. I separated the two, in order to make it possible to
5369        slip a promotion popup in between. But that it always needs two
5370        calls, to the first part, (now called UserMoveTest() ), and to
5371        FinishMove if the first part succeeded. Calls that do not need
5372        to do anything in between, can call this routine the old way. 
5373     */
5374     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5375 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5376     if(moveType == AmbiguousMove)
5377         DrawPosition(FALSE, boards[currentMove]);
5378     else if(moveType != ImpossibleMove && moveType != Comment)
5379         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5380 }
5381
5382 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5383 {
5384 //    char * hint = lastHint;
5385     FrontEndProgramStats stats;
5386
5387     stats.which = cps == &first ? 0 : 1;
5388     stats.depth = cpstats->depth;
5389     stats.nodes = cpstats->nodes;
5390     stats.score = cpstats->score;
5391     stats.time = cpstats->time;
5392     stats.pv = cpstats->movelist;
5393     stats.hint = lastHint;
5394     stats.an_move_index = 0;
5395     stats.an_move_count = 0;
5396
5397     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5398         stats.hint = cpstats->move_name;
5399         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5400         stats.an_move_count = cpstats->nr_moves;
5401     }
5402
5403     SetProgramStats( &stats );
5404 }
5405
5406 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5407 {   // [HGM] book: this routine intercepts moves to simulate book replies
5408     char *bookHit = NULL;
5409
5410     //first determine if the incoming move brings opponent into his book
5411     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5412         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5413     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5414     if(bookHit != NULL && !cps->bookSuspend) {
5415         // make sure opponent is not going to reply after receiving move to book position
5416         SendToProgram("force\n", cps);
5417         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5418     }
5419     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5420     // now arrange restart after book miss
5421     if(bookHit) {
5422         // after a book hit we never send 'go', and the code after the call to this routine
5423         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5424         char buf[MSG_SIZ];
5425         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5426         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5427         SendToProgram(buf, cps);
5428         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5429     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5430         SendToProgram("go\n", cps);
5431         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5432     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5433         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5434             SendToProgram("go\n", cps); 
5435         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5436     }
5437     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5438 }
5439
5440 char *savedMessage;
5441 ChessProgramState *savedState;
5442 void DeferredBookMove(void)
5443 {
5444         if(savedState->lastPing != savedState->lastPong)
5445                     ScheduleDelayedEvent(DeferredBookMove, 10);
5446         else
5447         HandleMachineMove(savedMessage, savedState);
5448 }
5449
5450 void
5451 HandleMachineMove(message, cps)
5452      char *message;
5453      ChessProgramState *cps;
5454 {
5455     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5456     char realname[MSG_SIZ];
5457     int fromX, fromY, toX, toY;
5458     ChessMove moveType;
5459     char promoChar;
5460     char *p;
5461     int machineWhite;
5462     char *bookHit;
5463
5464 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5465     /*
5466      * Kludge to ignore BEL characters
5467      */
5468     while (*message == '\007') message++;
5469
5470     /*
5471      * [HGM] engine debug message: ignore lines starting with '#' character
5472      */
5473     if(cps->debug && *message == '#') return;
5474
5475     /*
5476      * Look for book output
5477      */
5478     if (cps == &first && bookRequested) {
5479         if (message[0] == '\t' || message[0] == ' ') {
5480             /* Part of the book output is here; append it */
5481             strcat(bookOutput, message);
5482             strcat(bookOutput, "  \n");
5483             return;
5484         } else if (bookOutput[0] != NULLCHAR) {
5485             /* All of book output has arrived; display it */
5486             char *p = bookOutput;
5487             while (*p != NULLCHAR) {
5488                 if (*p == '\t') *p = ' ';
5489                 p++;
5490             }
5491             DisplayInformation(bookOutput);
5492             bookRequested = FALSE;
5493             /* Fall through to parse the current output */
5494         }
5495     }
5496
5497     /*
5498      * Look for machine move.
5499      */
5500     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5501         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5502     {
5503         /* This method is only useful on engines that support ping */
5504         if (cps->lastPing != cps->lastPong) {
5505           if (gameMode == BeginningOfGame) {
5506             /* Extra move from before last new; ignore */
5507             if (appData.debugMode) {
5508                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5509             }
5510           } else {
5511             if (appData.debugMode) {
5512                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5513                         cps->which, gameMode);
5514             }
5515
5516             SendToProgram("undo\n", cps);
5517           }
5518           return;
5519         }
5520
5521         switch (gameMode) {
5522           case BeginningOfGame:
5523             /* Extra move from before last reset; ignore */
5524             if (appData.debugMode) {
5525                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5526             }
5527             return;
5528
5529           case EndOfGame:
5530           case IcsIdle:
5531           default:
5532             /* Extra move after we tried to stop.  The mode test is
5533                not a reliable way of detecting this problem, but it's
5534                the best we can do on engines that don't support ping.
5535             */
5536             if (appData.debugMode) {
5537                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5538                         cps->which, gameMode);
5539             }
5540             SendToProgram("undo\n", cps);
5541             return;
5542
5543           case MachinePlaysWhite:
5544           case IcsPlayingWhite:
5545             machineWhite = TRUE;
5546             break;
5547
5548           case MachinePlaysBlack:
5549           case IcsPlayingBlack:
5550             machineWhite = FALSE;
5551             break;
5552
5553           case TwoMachinesPlay:
5554             machineWhite = (cps->twoMachinesColor[0] == 'w');
5555             break;
5556         }
5557         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5558             if (appData.debugMode) {
5559                 fprintf(debugFP,
5560                         "Ignoring move out of turn by %s, gameMode %d"
5561                         ", forwardMost %d\n",
5562                         cps->which, gameMode, forwardMostMove);
5563             }
5564             return;
5565         }
5566
5567     if (appData.debugMode) { int f = forwardMostMove;
5568         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5569                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5570     }
5571         if(cps->alphaRank) AlphaRank(machineMove, 4);
5572         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5573                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5574             /* Machine move could not be parsed; ignore it. */
5575             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5576                     machineMove, cps->which);
5577             DisplayError(buf1, 0);
5578             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5579                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5580             if (gameMode == TwoMachinesPlay) {
5581               GameEnds(machineWhite ? BlackWins : WhiteWins,
5582                        buf1, GE_XBOARD);
5583             }
5584             return;
5585         }
5586
5587         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5588         /* So we have to redo legality test with true e.p. status here,  */
5589         /* to make sure an illegal e.p. capture does not slip through,   */
5590         /* to cause a forfeit on a justified illegal-move complaint      */
5591         /* of the opponent.                                              */
5592         if( gameMode==TwoMachinesPlay && appData.testLegality
5593             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5594                                                               ) {
5595            ChessMove moveType;
5596            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5597                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5598                              fromY, fromX, toY, toX, promoChar);
5599             if (appData.debugMode) {
5600                 int i;
5601                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5602                     castlingRights[forwardMostMove][i], castlingRank[i]);
5603                 fprintf(debugFP, "castling rights\n");
5604             }
5605             if(moveType == IllegalMove) {
5606                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5607                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5608                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5609                            buf1, GE_XBOARD);
5610                 return;
5611            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5612            /* [HGM] Kludge to handle engines that send FRC-style castling
5613               when they shouldn't (like TSCP-Gothic) */
5614            switch(moveType) {
5615              case WhiteASideCastleFR:
5616              case BlackASideCastleFR:
5617                toX+=2;
5618                currentMoveString[2]++;
5619                break;
5620              case WhiteHSideCastleFR:
5621              case BlackHSideCastleFR:
5622                toX--;
5623                currentMoveString[2]--;
5624                break;
5625              default: ; // nothing to do, but suppresses warning of pedantic compilers
5626            }
5627         }
5628         hintRequested = FALSE;
5629         lastHint[0] = NULLCHAR;
5630         bookRequested = FALSE;
5631         /* Program may be pondering now */
5632         cps->maybeThinking = TRUE;
5633         if (cps->sendTime == 2) cps->sendTime = 1;
5634         if (cps->offeredDraw) cps->offeredDraw--;
5635
5636 #if ZIPPY
5637         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5638             first.initDone) {
5639           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5640           ics_user_moved = 1;
5641           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5642                 char buf[3*MSG_SIZ];
5643
5644                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5645                         programStats.score / 100.,
5646                         programStats.depth,
5647                         programStats.time / 100.,
5648                         (unsigned int)programStats.nodes,
5649                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5650                         programStats.movelist);
5651                 SendToICS(buf);
5652 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5653           }
5654         }
5655 #endif
5656         /* currentMoveString is set as a side-effect of ParseOneMove */
5657         strcpy(machineMove, currentMoveString);
5658         strcat(machineMove, "\n");
5659         strcpy(moveList[forwardMostMove], machineMove);
5660
5661         /* [AS] Save move info and clear stats for next move */
5662         pvInfoList[ forwardMostMove ].score = programStats.score;
5663         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5664         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5665         ClearProgramStats();
5666         thinkOutput[0] = NULLCHAR;
5667         hiddenThinkOutputState = 0;
5668
5669         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5670
5671         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5672         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5673             int count = 0;
5674
5675             while( count < adjudicateLossPlies ) {
5676                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5677
5678                 if( count & 1 ) {
5679                     score = -score; /* Flip score for winning side */
5680                 }
5681
5682                 if( score > adjudicateLossThreshold ) {
5683                     break;
5684                 }
5685
5686                 count++;
5687             }
5688
5689             if( count >= adjudicateLossPlies ) {
5690                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5691
5692                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5693                     "Xboard adjudication", 
5694                     GE_XBOARD );
5695
5696                 return;
5697             }
5698         }
5699
5700         if( gameMode == TwoMachinesPlay ) {
5701           // [HGM] some adjudications useful with buggy engines
5702             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5703           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5704
5705
5706             if( appData.testLegality )
5707             {   /* [HGM] Some more adjudications for obstinate engines */
5708                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5709                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5710                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5711                 static int moveCount = 6;
5712                 ChessMove result;
5713                 char *reason = NULL;
5714
5715                 /* Count what is on board. */
5716                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5717                 {   ChessSquare p = boards[forwardMostMove][i][j];
5718                     int m=i;
5719
5720                     switch((int) p)
5721                     {   /* count B,N,R and other of each side */
5722                         case WhiteKing:
5723                         case BlackKing:
5724                              NrK++; break; // [HGM] atomic: count Kings
5725                         case WhiteKnight:
5726                              NrWN++; break;
5727                         case WhiteBishop:
5728                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5729                              bishopsColor |= 1 << ((i^j)&1);
5730                              NrWB++; break;
5731                         case BlackKnight:
5732                              NrBN++; break;
5733                         case BlackBishop:
5734                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5735                              bishopsColor |= 1 << ((i^j)&1);
5736                              NrBB++; break;
5737                         case WhiteRook:
5738                              NrWR++; break;
5739                         case BlackRook:
5740                              NrBR++; break;
5741                         case WhiteQueen:
5742                              NrWQ++; break;
5743                         case BlackQueen:
5744                              NrBQ++; break;
5745                         case EmptySquare: 
5746                              break;
5747                         case BlackPawn:
5748                              m = 7-i;
5749                         case WhitePawn:
5750                              PawnAdvance += m; NrPawns++;
5751                     }
5752                     NrPieces += (p != EmptySquare);
5753                     NrW += ((int)p < (int)BlackPawn);
5754                     if(gameInfo.variant == VariantXiangqi && 
5755                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5756                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5757                         NrW -= ((int)p < (int)BlackPawn);
5758                     }
5759                 }
5760
5761                 /* Some material-based adjudications that have to be made before stalemate test */
5762                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5763                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5764                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5765                      if(appData.checkMates) {
5766                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5767                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5768                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5769                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5770                          return;
5771                      }
5772                 }
5773
5774                 /* Bare King in Shatranj (loses) or Losers (wins) */
5775                 if( NrW == 1 || NrPieces - NrW == 1) {
5776                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5777                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5778                      if(appData.checkMates) {
5779                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5780                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5781                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5782                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5783                          return;
5784                      }
5785                   } else
5786                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5787                   {    /* bare King */
5788                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5789                         if(appData.checkMates) {
5790                             /* but only adjudicate if adjudication enabled */
5791                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5792                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5793                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5794                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5795                             return;
5796                         }
5797                   }
5798                 } else bare = 1;
5799
5800
5801             // don't wait for engine to announce game end if we can judge ourselves
5802             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5803                                        castlingRights[forwardMostMove]) ) {
5804               case MT_CHECK:
5805                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5806                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5807                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5808                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5809                             checkCnt++;
5810                         if(checkCnt >= 2) {
5811                             reason = "Xboard adjudication: 3rd check";
5812                             epStatus[forwardMostMove] = EP_CHECKMATE;
5813                             break;
5814                         }
5815                     }
5816                 }
5817               case MT_NONE:
5818               default:
5819                 break;
5820               case MT_STALEMATE:
5821               case MT_STAINMATE:
5822                 reason = "Xboard adjudication: Stalemate";
5823                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5824                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5825                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5826                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5827                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5828                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5829                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5830                                                                         EP_CHECKMATE : EP_WINS);
5831                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5832                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5833                 }
5834                 break;
5835               case MT_CHECKMATE:
5836                 reason = "Xboard adjudication: Checkmate";
5837                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5838                 break;
5839             }
5840
5841                 switch(i = epStatus[forwardMostMove]) {
5842                     case EP_STALEMATE:
5843                         result = GameIsDrawn; break;
5844                     case EP_CHECKMATE:
5845                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5846                     case EP_WINS:
5847                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5848                     default:
5849                         result = (ChessMove) 0;
5850                 }
5851                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5852                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5853                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5854                     GameEnds( result, reason, GE_XBOARD );
5855                     return;
5856                 }
5857
5858                 /* Next absolutely insufficient mating material. */
5859                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5860                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5861                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5862                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5863                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5864
5865                      /* always flag draws, for judging claims */
5866                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5867
5868                      if(appData.materialDraws) {
5869                          /* but only adjudicate them if adjudication enabled */
5870                          SendToProgram("force\n", cps->other); // suppress reply
5871                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5872                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5873                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5874                          return;
5875                      }
5876                 }
5877
5878                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5879                 if(NrPieces == 4 && 
5880                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5881                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5882                    || NrWN==2 || NrBN==2     /* KNNK */
5883                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5884                   ) ) {
5885                      if(--moveCount < 0 && appData.trivialDraws)
5886                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5887                           SendToProgram("force\n", cps->other); // suppress reply
5888                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5889                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5890                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5891                           return;
5892                      }
5893                 } else moveCount = 6;
5894             }
5895           }
5896           
5897           if (appData.debugMode) { int i;
5898             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5899                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5900                     appData.drawRepeats);
5901             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5902               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5903             
5904           }
5905
5906                 /* Check for rep-draws */
5907                 count = 0;
5908                 for(k = forwardMostMove-2;
5909                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5910                         epStatus[k] < EP_UNKNOWN &&
5911                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5912                     k-=2)
5913                 {   int rights=0;
5914                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5915                         /* compare castling rights */
5916                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5917                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5918                                 rights++; /* King lost rights, while rook still had them */
5919                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5920                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5921                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5922                                    rights++; /* but at least one rook lost them */
5923                         }
5924                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5925                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5926                                 rights++; 
5927                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5928                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5929                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5930                                    rights++;
5931                         }
5932                         if( rights == 0 && ++count > appData.drawRepeats-2
5933                             && appData.drawRepeats > 1) {
5934                              /* adjudicate after user-specified nr of repeats */
5935                              SendToProgram("force\n", cps->other); // suppress reply
5936                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5937                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5938                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5939                                 // [HGM] xiangqi: check for forbidden perpetuals
5940                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5941                                 for(m=forwardMostMove; m>k; m-=2) {
5942                                     if(MateTest(boards[m], PosFlags(m), 
5943                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5944                                         ourPerpetual = 0; // the current mover did not always check
5945                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5946                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5947                                         hisPerpetual = 0; // the opponent did not always check
5948                                 }
5949                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5950                                                                         ourPerpetual, hisPerpetual);
5951                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5952                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5953                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5954                                     return;
5955                                 }
5956                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5957                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5958                                 // Now check for perpetual chases
5959                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5960                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5961                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5962                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5963                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5964                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5965                                         return;
5966                                     }
5967                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5968                                         break; // Abort repetition-checking loop.
5969                                 }
5970                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5971                              }
5972                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5973                              return;
5974                         }
5975                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5976                              epStatus[forwardMostMove] = EP_REP_DRAW;
5977                     }
5978                 }
5979
5980                 /* Now we test for 50-move draws. Determine ply count */
5981                 count = forwardMostMove;
5982                 /* look for last irreversble move */
5983                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5984                     count--;
5985                 /* if we hit starting position, add initial plies */
5986                 if( count == backwardMostMove )
5987                     count -= initialRulePlies;
5988                 count = forwardMostMove - count; 
5989                 if( count >= 100)
5990                          epStatus[forwardMostMove] = EP_RULE_DRAW;
5991                          /* this is used to judge if draw claims are legal */
5992                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5993                          SendToProgram("force\n", cps->other); // suppress reply
5994                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5995                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5996                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
5997                          return;
5998                 }
5999
6000                 /* if draw offer is pending, treat it as a draw claim
6001                  * when draw condition present, to allow engines a way to
6002                  * claim draws before making their move to avoid a race
6003                  * condition occurring after their move
6004                  */
6005                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6006                          char *p = NULL;
6007                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6008                              p = "Draw claim: 50-move rule";
6009                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6010                              p = "Draw claim: 3-fold repetition";
6011                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6012                              p = "Draw claim: insufficient mating material";
6013                          if( p != NULL ) {
6014                              SendToProgram("force\n", cps->other); // suppress reply
6015                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6016                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6017                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6018                              return;
6019                          }
6020                 }
6021
6022
6023                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6024                     SendToProgram("force\n", cps->other); // suppress reply
6025                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6026                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6027
6028                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6029
6030                     return;
6031                 }
6032         }
6033
6034         bookHit = NULL;
6035         if (gameMode == TwoMachinesPlay) {
6036             /* [HGM] relaying draw offers moved to after reception of move */
6037             /* and interpreting offer as claim if it brings draw condition */
6038             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6039                 SendToProgram("draw\n", cps->other);
6040             }
6041             if (cps->other->sendTime) {
6042                 SendTimeRemaining(cps->other,
6043                                   cps->other->twoMachinesColor[0] == 'w');
6044             }
6045             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6046             if (firstMove && !bookHit) {
6047                 firstMove = FALSE;
6048                 if (cps->other->useColors) {
6049                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6050                 }
6051                 SendToProgram("go\n", cps->other);
6052             }
6053             cps->other->maybeThinking = TRUE;
6054         }
6055
6056         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6057         
6058         if (!pausing && appData.ringBellAfterMoves) {
6059             RingBell();
6060         }
6061
6062         /* 
6063          * Reenable menu items that were disabled while
6064          * machine was thinking
6065          */
6066         if (gameMode != TwoMachinesPlay)
6067             SetUserThinkingEnables();
6068
6069         // [HGM] book: after book hit opponent has received move and is now in force mode
6070         // force the book reply into it, and then fake that it outputted this move by jumping
6071         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6072         if(bookHit) {
6073                 static char bookMove[MSG_SIZ]; // a bit generous?
6074
6075                 strcpy(bookMove, "move ");
6076                 strcat(bookMove, bookHit);
6077                 message = bookMove;
6078                 cps = cps->other;
6079                 programStats.nodes = programStats.depth = programStats.time = 
6080                 programStats.score = programStats.got_only_move = 0;
6081                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6082
6083                 if(cps->lastPing != cps->lastPong) {
6084                     savedMessage = message; // args for deferred call
6085                     savedState = cps;
6086                     ScheduleDelayedEvent(DeferredBookMove, 10);
6087                     return;
6088                 }
6089                 goto FakeBookMove;
6090         }
6091
6092         return;
6093     }
6094
6095     /* Set special modes for chess engines.  Later something general
6096      *  could be added here; for now there is just one kludge feature,
6097      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6098      *  when "xboard" is given as an interactive command.
6099      */
6100     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6101         cps->useSigint = FALSE;
6102         cps->useSigterm = FALSE;
6103     }
6104     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6105       ParseFeatures(message+8, cps);
6106       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6107     }
6108
6109     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6110      * want this, I was asked to put it in, and obliged.
6111      */
6112     if (!strncmp(message, "setboard ", 9)) {
6113         Board initial_position; int i;
6114
6115         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6116
6117         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6118             DisplayError(_("Bad FEN received from engine"), 0);
6119             return ;
6120         } else {
6121            Reset(FALSE, FALSE);
6122            CopyBoard(boards[0], initial_position);
6123            initialRulePlies = FENrulePlies;
6124            epStatus[0] = FENepStatus;
6125            for( i=0; i<nrCastlingRights; i++ )
6126                 castlingRights[0][i] = FENcastlingRights[i];
6127            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6128            else gameMode = MachinePlaysBlack;                 
6129            DrawPosition(FALSE, boards[currentMove]);
6130         }
6131         return;
6132     }
6133
6134     /*
6135      * Look for communication commands
6136      */
6137     if (!strncmp(message, "telluser ", 9)) {
6138         DisplayNote(message + 9);
6139         return;
6140     }
6141     if (!strncmp(message, "tellusererror ", 14)) {
6142         DisplayError(message + 14, 0);
6143         return;
6144     }
6145     if (!strncmp(message, "tellopponent ", 13)) {
6146       if (appData.icsActive) {
6147         if (loggedOn) {
6148           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6149           SendToICS(buf1);
6150         }
6151       } else {
6152         DisplayNote(message + 13);
6153       }
6154       return;
6155     }
6156     if (!strncmp(message, "tellothers ", 11)) {
6157       if (appData.icsActive) {
6158         if (loggedOn) {
6159           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6160           SendToICS(buf1);
6161         }
6162       }
6163       return;
6164     }
6165     if (!strncmp(message, "tellall ", 8)) {
6166       if (appData.icsActive) {
6167         if (loggedOn) {
6168           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6169           SendToICS(buf1);
6170         }
6171       } else {
6172         DisplayNote(message + 8);
6173       }
6174       return;
6175     }
6176     if (strncmp(message, "warning", 7) == 0) {
6177         /* Undocumented feature, use tellusererror in new code */
6178         DisplayError(message, 0);
6179         return;
6180     }
6181     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6182         strcpy(realname, cps->tidy);
6183         strcat(realname, " query");
6184         AskQuestion(realname, buf2, buf1, cps->pr);
6185         return;
6186     }
6187     /* Commands from the engine directly to ICS.  We don't allow these to be 
6188      *  sent until we are logged on. Crafty kibitzes have been known to 
6189      *  interfere with the login process.
6190      */
6191     if (loggedOn) {
6192         if (!strncmp(message, "tellics ", 8)) {
6193             SendToICS(message + 8);
6194             SendToICS("\n");
6195             return;
6196         }
6197         if (!strncmp(message, "tellicsnoalias ", 15)) {
6198             SendToICS(ics_prefix);
6199             SendToICS(message + 15);
6200             SendToICS("\n");
6201             return;
6202         }
6203         /* The following are for backward compatibility only */
6204         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6205             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6206             SendToICS(ics_prefix);
6207             SendToICS(message);
6208             SendToICS("\n");
6209             return;
6210         }
6211     }
6212     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6213         return;
6214     }
6215     /*
6216      * If the move is illegal, cancel it and redraw the board.
6217      * Also deal with other error cases.  Matching is rather loose
6218      * here to accommodate engines written before the spec.
6219      */
6220     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6221         strncmp(message, "Error", 5) == 0) {
6222         if (StrStr(message, "name") || 
6223             StrStr(message, "rating") || StrStr(message, "?") ||
6224             StrStr(message, "result") || StrStr(message, "board") ||
6225             StrStr(message, "bk") || StrStr(message, "computer") ||
6226             StrStr(message, "variant") || StrStr(message, "hint") ||
6227             StrStr(message, "random") || StrStr(message, "depth") ||
6228             StrStr(message, "accepted")) {
6229             return;
6230         }
6231         if (StrStr(message, "protover")) {
6232           /* Program is responding to input, so it's apparently done
6233              initializing, and this error message indicates it is
6234              protocol version 1.  So we don't need to wait any longer
6235              for it to initialize and send feature commands. */
6236           FeatureDone(cps, 1);
6237           cps->protocolVersion = 1;
6238           return;
6239         }
6240         cps->maybeThinking = FALSE;
6241
6242         if (StrStr(message, "draw")) {
6243             /* Program doesn't have "draw" command */
6244             cps->sendDrawOffers = 0;
6245             return;
6246         }
6247         if (cps->sendTime != 1 &&
6248             (StrStr(message, "time") || StrStr(message, "otim"))) {
6249           /* Program apparently doesn't have "time" or "otim" command */
6250           cps->sendTime = 0;
6251           return;
6252         }
6253         if (StrStr(message, "analyze")) {
6254             cps->analysisSupport = FALSE;
6255             cps->analyzing = FALSE;
6256             Reset(FALSE, TRUE);
6257             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6258             DisplayError(buf2, 0);
6259             return;
6260         }
6261         if (StrStr(message, "(no matching move)st")) {
6262           /* Special kludge for GNU Chess 4 only */
6263           cps->stKludge = TRUE;
6264           SendTimeControl(cps, movesPerSession, timeControl,
6265                           timeIncrement, appData.searchDepth,
6266                           searchTime);
6267           return;
6268         }
6269         if (StrStr(message, "(no matching move)sd")) {
6270           /* Special kludge for GNU Chess 4 only */
6271           cps->sdKludge = TRUE;
6272           SendTimeControl(cps, movesPerSession, timeControl,
6273                           timeIncrement, appData.searchDepth,
6274                           searchTime);
6275           return;
6276         }
6277         if (!StrStr(message, "llegal")) {
6278             return;
6279         }
6280         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6281             gameMode == IcsIdle) return;
6282         if (forwardMostMove <= backwardMostMove) return;
6283         if (pausing) PauseEvent();
6284       if(appData.forceIllegal) {
6285             // [HGM] illegal: machine refused move; force position after move into it
6286           SendToProgram("force\n", cps);
6287           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6288                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6289                 // when black is to move, while there might be nothing on a2 or black
6290                 // might already have the move. So send the board as if white has the move.
6291                 // But first we must change the stm of the engine, as it refused the last move
6292                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6293                 if(WhiteOnMove(forwardMostMove)) {
6294                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6295                     SendBoard(cps, forwardMostMove); // kludgeless board
6296                 } else {
6297                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6298                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6299                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6300                 }
6301           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6302             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6303                  gameMode == TwoMachinesPlay)
6304               SendToProgram("go\n", cps);
6305             return;
6306       } else
6307         if (gameMode == PlayFromGameFile) {
6308             /* Stop reading this game file */
6309             gameMode = EditGame;
6310             ModeHighlight();
6311         }
6312         currentMove = --forwardMostMove;
6313         DisplayMove(currentMove-1); /* before DisplayMoveError */
6314         SwitchClocks();
6315         DisplayBothClocks();
6316         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6317                 parseList[currentMove], cps->which);
6318         DisplayMoveError(buf1);
6319         DrawPosition(FALSE, boards[currentMove]);
6320
6321         /* [HGM] illegal-move claim should forfeit game when Xboard */
6322         /* only passes fully legal moves                            */
6323         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6324             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6325                                 "False illegal-move claim", GE_XBOARD );
6326         }
6327         return;
6328     }
6329     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6330         /* Program has a broken "time" command that
6331            outputs a string not ending in newline.
6332            Don't use it. */
6333         cps->sendTime = 0;
6334     }
6335     
6336     /*
6337      * If chess program startup fails, exit with an error message.
6338      * Attempts to recover here are futile.
6339      */
6340     if ((StrStr(message, "unknown host") != NULL)
6341         || (StrStr(message, "No remote directory") != NULL)
6342         || (StrStr(message, "not found") != NULL)
6343         || (StrStr(message, "No such file") != NULL)
6344         || (StrStr(message, "can't alloc") != NULL)
6345         || (StrStr(message, "Permission denied") != NULL)) {
6346
6347         cps->maybeThinking = FALSE;
6348         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6349                 cps->which, cps->program, cps->host, message);
6350         RemoveInputSource(cps->isr);
6351         DisplayFatalError(buf1, 0, 1);
6352         return;
6353     }
6354     
6355     /* 
6356      * Look for hint output
6357      */
6358     if (sscanf(message, "Hint: %s", buf1) == 1) {
6359         if (cps == &first && hintRequested) {
6360             hintRequested = FALSE;
6361             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6362                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6363                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6364                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6365                                     fromY, fromX, toY, toX, promoChar, buf1);
6366                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6367                 DisplayInformation(buf2);
6368             } else {
6369                 /* Hint move could not be parsed!? */
6370               snprintf(buf2, sizeof(buf2),
6371                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6372                         buf1, cps->which);
6373                 DisplayError(buf2, 0);
6374             }
6375         } else {
6376             strcpy(lastHint, buf1);
6377         }
6378         return;
6379     }
6380
6381     /*
6382      * Ignore other messages if game is not in progress
6383      */
6384     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6385         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6386
6387     /*
6388      * look for win, lose, draw, or draw offer
6389      */
6390     if (strncmp(message, "1-0", 3) == 0) {
6391         char *p, *q, *r = "";
6392         p = strchr(message, '{');
6393         if (p) {
6394             q = strchr(p, '}');
6395             if (q) {
6396                 *q = NULLCHAR;
6397                 r = p + 1;
6398             }
6399         }
6400         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6401         return;
6402     } else if (strncmp(message, "0-1", 3) == 0) {
6403         char *p, *q, *r = "";
6404         p = strchr(message, '{');
6405         if (p) {
6406             q = strchr(p, '}');
6407             if (q) {
6408                 *q = NULLCHAR;
6409                 r = p + 1;
6410             }
6411         }
6412         /* Kludge for Arasan 4.1 bug */
6413         if (strcmp(r, "Black resigns") == 0) {
6414             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6415             return;
6416         }
6417         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6418         return;
6419     } else if (strncmp(message, "1/2", 3) == 0) {
6420         char *p, *q, *r = "";
6421         p = strchr(message, '{');
6422         if (p) {
6423             q = strchr(p, '}');
6424             if (q) {
6425                 *q = NULLCHAR;
6426                 r = p + 1;
6427             }
6428         }
6429             
6430         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6431         return;
6432
6433     } else if (strncmp(message, "White resign", 12) == 0) {
6434         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6435         return;
6436     } else if (strncmp(message, "Black resign", 12) == 0) {
6437         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6438         return;
6439     } else if (strncmp(message, "White matches", 13) == 0 ||
6440                strncmp(message, "Black matches", 13) == 0   ) {
6441         /* [HGM] ignore GNUShogi noises */
6442         return;
6443     } else if (strncmp(message, "White", 5) == 0 &&
6444                message[5] != '(' &&
6445                StrStr(message, "Black") == NULL) {
6446         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6447         return;
6448     } else if (strncmp(message, "Black", 5) == 0 &&
6449                message[5] != '(') {
6450         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6451         return;
6452     } else if (strcmp(message, "resign") == 0 ||
6453                strcmp(message, "computer resigns") == 0) {
6454         switch (gameMode) {
6455           case MachinePlaysBlack:
6456           case IcsPlayingBlack:
6457             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6458             break;
6459           case MachinePlaysWhite:
6460           case IcsPlayingWhite:
6461             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6462             break;
6463           case TwoMachinesPlay:
6464             if (cps->twoMachinesColor[0] == 'w')
6465               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6466             else
6467               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6468             break;
6469           default:
6470             /* can't happen */
6471             break;
6472         }
6473         return;
6474     } else if (strncmp(message, "opponent mates", 14) == 0) {
6475         switch (gameMode) {
6476           case MachinePlaysBlack:
6477           case IcsPlayingBlack:
6478             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6479             break;
6480           case MachinePlaysWhite:
6481           case IcsPlayingWhite:
6482             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6483             break;
6484           case TwoMachinesPlay:
6485             if (cps->twoMachinesColor[0] == 'w')
6486               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6487             else
6488               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6489             break;
6490           default:
6491             /* can't happen */
6492             break;
6493         }
6494         return;
6495     } else if (strncmp(message, "computer mates", 14) == 0) {
6496         switch (gameMode) {
6497           case MachinePlaysBlack:
6498           case IcsPlayingBlack:
6499             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6500             break;
6501           case MachinePlaysWhite:
6502           case IcsPlayingWhite:
6503             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6504             break;
6505           case TwoMachinesPlay:
6506             if (cps->twoMachinesColor[0] == 'w')
6507               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6508             else
6509               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6510             break;
6511           default:
6512             /* can't happen */
6513             break;
6514         }
6515         return;
6516     } else if (strncmp(message, "checkmate", 9) == 0) {
6517         if (WhiteOnMove(forwardMostMove)) {
6518             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6519         } else {
6520             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6521         }
6522         return;
6523     } else if (strstr(message, "Draw") != NULL ||
6524                strstr(message, "game is a draw") != NULL) {
6525         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6526         return;
6527     } else if (strstr(message, "offer") != NULL &&
6528                strstr(message, "draw") != NULL) {
6529 #if ZIPPY
6530         if (appData.zippyPlay && first.initDone) {
6531             /* Relay offer to ICS */
6532             SendToICS(ics_prefix);
6533             SendToICS("draw\n");
6534         }
6535 #endif
6536         cps->offeredDraw = 2; /* valid until this engine moves twice */
6537         if (gameMode == TwoMachinesPlay) {
6538             if (cps->other->offeredDraw) {
6539                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6540             /* [HGM] in two-machine mode we delay relaying draw offer      */
6541             /* until after we also have move, to see if it is really claim */
6542             }
6543         } else if (gameMode == MachinePlaysWhite ||
6544                    gameMode == MachinePlaysBlack) {
6545           if (userOfferedDraw) {
6546             DisplayInformation(_("Machine accepts your draw offer"));
6547             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6548           } else {
6549             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6550           }
6551         }
6552     }
6553
6554     
6555     /*
6556      * Look for thinking output
6557      */
6558     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6559           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6560                                 ) {
6561         int plylev, mvleft, mvtot, curscore, time;
6562         char mvname[MOVE_LEN];
6563         u64 nodes; // [DM]
6564         char plyext;
6565         int ignore = FALSE;
6566         int prefixHint = FALSE;
6567         mvname[0] = NULLCHAR;
6568
6569         switch (gameMode) {
6570           case MachinePlaysBlack:
6571           case IcsPlayingBlack:
6572             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6573             break;
6574           case MachinePlaysWhite:
6575           case IcsPlayingWhite:
6576             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6577             break;
6578           case AnalyzeMode:
6579           case AnalyzeFile:
6580             break;
6581           case IcsObserving: /* [DM] icsEngineAnalyze */
6582             if (!appData.icsEngineAnalyze) ignore = TRUE;
6583             break;
6584           case TwoMachinesPlay:
6585             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6586                 ignore = TRUE;
6587             }
6588             break;
6589           default:
6590             ignore = TRUE;
6591             break;
6592         }
6593
6594         if (!ignore) {
6595             buf1[0] = NULLCHAR;
6596             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6597                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6598
6599                 if (plyext != ' ' && plyext != '\t') {
6600                     time *= 100;
6601                 }
6602
6603                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6604                 if( cps->scoreIsAbsolute && 
6605                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6606                 {
6607                     curscore = -curscore;
6608                 }
6609
6610
6611                 programStats.depth = plylev;
6612                 programStats.nodes = nodes;
6613                 programStats.time = time;
6614                 programStats.score = curscore;
6615                 programStats.got_only_move = 0;
6616
6617                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6618                         int ticklen;
6619
6620                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6621                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6622                         if(WhiteOnMove(forwardMostMove)) 
6623                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6624                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6625                 }
6626
6627                 /* Buffer overflow protection */
6628                 if (buf1[0] != NULLCHAR) {
6629                     if (strlen(buf1) >= sizeof(programStats.movelist)
6630                         && appData.debugMode) {
6631                         fprintf(debugFP,
6632                                 "PV is too long; using the first %d bytes.\n",
6633                                 sizeof(programStats.movelist) - 1);
6634                     }
6635
6636                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6637                 } else {
6638                     sprintf(programStats.movelist, " no PV\n");
6639                 }
6640
6641                 if (programStats.seen_stat) {
6642                     programStats.ok_to_send = 1;
6643                 }
6644
6645                 if (strchr(programStats.movelist, '(') != NULL) {
6646                     programStats.line_is_book = 1;
6647                     programStats.nr_moves = 0;
6648                     programStats.moves_left = 0;
6649                 } else {
6650                     programStats.line_is_book = 0;
6651                 }
6652
6653                 SendProgramStatsToFrontend( cps, &programStats );
6654
6655                 /* 
6656                     [AS] Protect the thinkOutput buffer from overflow... this
6657                     is only useful if buf1 hasn't overflowed first!
6658                 */
6659                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6660                         plylev, 
6661                         (gameMode == TwoMachinesPlay ?
6662                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6663                         ((double) curscore) / 100.0,
6664                         prefixHint ? lastHint : "",
6665                         prefixHint ? " " : "" );
6666
6667                 if( buf1[0] != NULLCHAR ) {
6668                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6669
6670                     if( strlen(buf1) > max_len ) {
6671                         if( appData.debugMode) {
6672                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6673                         }
6674                         buf1[max_len+1] = '\0';
6675                     }
6676
6677                     strcat( thinkOutput, buf1 );
6678                 }
6679
6680                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6681                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6682                     DisplayMove(currentMove - 1);
6683                     DisplayAnalysis();
6684                 }
6685                 return;
6686
6687             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6688                 /* crafty (9.25+) says "(only move) <move>"
6689                  * if there is only 1 legal move
6690                  */
6691                 sscanf(p, "(only move) %s", buf1);
6692                 sprintf(thinkOutput, "%s (only move)", buf1);
6693                 sprintf(programStats.movelist, "%s (only move)", buf1);
6694                 programStats.depth = 1;
6695                 programStats.nr_moves = 1;
6696                 programStats.moves_left = 1;
6697                 programStats.nodes = 1;
6698                 programStats.time = 1;
6699                 programStats.got_only_move = 1;
6700
6701                 /* Not really, but we also use this member to
6702                    mean "line isn't going to change" (Crafty
6703                    isn't searching, so stats won't change) */
6704                 programStats.line_is_book = 1;
6705
6706                 SendProgramStatsToFrontend( cps, &programStats );
6707                 
6708                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6709                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6710                     DisplayMove(currentMove - 1);
6711                     DisplayAnalysis();
6712                 }
6713                 return;
6714             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6715                               &time, &nodes, &plylev, &mvleft,
6716                               &mvtot, mvname) >= 5) {
6717                 /* The stat01: line is from Crafty (9.29+) in response
6718                    to the "." command */
6719                 programStats.seen_stat = 1;
6720                 cps->maybeThinking = TRUE;
6721
6722                 if (programStats.got_only_move || !appData.periodicUpdates)
6723                   return;
6724
6725                 programStats.depth = plylev;
6726                 programStats.time = time;
6727                 programStats.nodes = nodes;
6728                 programStats.moves_left = mvleft;
6729                 programStats.nr_moves = mvtot;
6730                 strcpy(programStats.move_name, mvname);
6731                 programStats.ok_to_send = 1;
6732                 programStats.movelist[0] = '\0';
6733
6734                 SendProgramStatsToFrontend( cps, &programStats );
6735
6736                 DisplayAnalysis();
6737                 return;
6738
6739             } else if (strncmp(message,"++",2) == 0) {
6740                 /* Crafty 9.29+ outputs this */
6741                 programStats.got_fail = 2;
6742                 return;
6743
6744             } else if (strncmp(message,"--",2) == 0) {
6745                 /* Crafty 9.29+ outputs this */
6746                 programStats.got_fail = 1;
6747                 return;
6748
6749             } else if (thinkOutput[0] != NULLCHAR &&
6750                        strncmp(message, "    ", 4) == 0) {
6751                 unsigned message_len;
6752
6753                 p = message;
6754                 while (*p && *p == ' ') p++;
6755
6756                 message_len = strlen( p );
6757
6758                 /* [AS] Avoid buffer overflow */
6759                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6760                     strcat(thinkOutput, " ");
6761                     strcat(thinkOutput, p);
6762                 }
6763
6764                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6765                     strcat(programStats.movelist, " ");
6766                     strcat(programStats.movelist, p);
6767                 }
6768
6769                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6770                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6771                     DisplayMove(currentMove - 1);
6772                     DisplayAnalysis();
6773                 }
6774                 return;
6775             }
6776         }
6777         else {
6778             buf1[0] = NULLCHAR;
6779
6780             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6781                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6782             {
6783                 ChessProgramStats cpstats;
6784
6785                 if (plyext != ' ' && plyext != '\t') {
6786                     time *= 100;
6787                 }
6788
6789                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6790                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6791                     curscore = -curscore;
6792                 }
6793
6794                 cpstats.depth = plylev;
6795                 cpstats.nodes = nodes;
6796                 cpstats.time = time;
6797                 cpstats.score = curscore;
6798                 cpstats.got_only_move = 0;
6799                 cpstats.movelist[0] = '\0';
6800
6801                 if (buf1[0] != NULLCHAR) {
6802                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6803                 }
6804
6805                 cpstats.ok_to_send = 0;
6806                 cpstats.line_is_book = 0;
6807                 cpstats.nr_moves = 0;
6808                 cpstats.moves_left = 0;
6809
6810                 SendProgramStatsToFrontend( cps, &cpstats );
6811             }
6812         }
6813     }
6814 }
6815
6816
6817 /* Parse a game score from the character string "game", and
6818    record it as the history of the current game.  The game
6819    score is NOT assumed to start from the standard position. 
6820    The display is not updated in any way.
6821    */
6822 void
6823 ParseGameHistory(game)
6824      char *game;
6825 {
6826     ChessMove moveType;
6827     int fromX, fromY, toX, toY, boardIndex;
6828     char promoChar;
6829     char *p, *q;
6830     char buf[MSG_SIZ];
6831
6832     if (appData.debugMode)
6833       fprintf(debugFP, "Parsing game history: %s\n", game);
6834
6835     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6836     gameInfo.site = StrSave(appData.icsHost);
6837     gameInfo.date = PGNDate();
6838     gameInfo.round = StrSave("-");
6839
6840     /* Parse out names of players */
6841     while (*game == ' ') game++;
6842     p = buf;
6843     while (*game != ' ') *p++ = *game++;
6844     *p = NULLCHAR;
6845     gameInfo.white = StrSave(buf);
6846     while (*game == ' ') game++;
6847     p = buf;
6848     while (*game != ' ' && *game != '\n') *p++ = *game++;
6849     *p = NULLCHAR;
6850     gameInfo.black = StrSave(buf);
6851
6852     /* Parse moves */
6853     boardIndex = blackPlaysFirst ? 1 : 0;
6854     yynewstr(game);
6855     for (;;) {
6856         yyboardindex = boardIndex;
6857         moveType = (ChessMove) yylex();
6858         switch (moveType) {
6859           case IllegalMove:             /* maybe suicide chess, etc. */
6860   if (appData.debugMode) {
6861     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6862     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6863     setbuf(debugFP, NULL);
6864   }
6865           case WhitePromotionChancellor:
6866           case BlackPromotionChancellor:
6867           case WhitePromotionArchbishop:
6868           case BlackPromotionArchbishop:
6869           case WhitePromotionQueen:
6870           case BlackPromotionQueen:
6871           case WhitePromotionRook:
6872           case BlackPromotionRook:
6873           case WhitePromotionBishop:
6874           case BlackPromotionBishop:
6875           case WhitePromotionKnight:
6876           case BlackPromotionKnight:
6877           case WhitePromotionKing:
6878           case BlackPromotionKing:
6879           case NormalMove:
6880           case WhiteCapturesEnPassant:
6881           case BlackCapturesEnPassant:
6882           case WhiteKingSideCastle:
6883           case WhiteQueenSideCastle:
6884           case BlackKingSideCastle:
6885           case BlackQueenSideCastle:
6886           case WhiteKingSideCastleWild:
6887           case WhiteQueenSideCastleWild:
6888           case BlackKingSideCastleWild:
6889           case BlackQueenSideCastleWild:
6890           /* PUSH Fabien */
6891           case WhiteHSideCastleFR:
6892           case WhiteASideCastleFR:
6893           case BlackHSideCastleFR:
6894           case BlackASideCastleFR:
6895           /* POP Fabien */
6896             fromX = currentMoveString[0] - AAA;
6897             fromY = currentMoveString[1] - ONE;
6898             toX = currentMoveString[2] - AAA;
6899             toY = currentMoveString[3] - ONE;
6900             promoChar = currentMoveString[4];
6901             break;
6902           case WhiteDrop:
6903           case BlackDrop:
6904             fromX = moveType == WhiteDrop ?
6905               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6906             (int) CharToPiece(ToLower(currentMoveString[0]));
6907             fromY = DROP_RANK;
6908             toX = currentMoveString[2] - AAA;
6909             toY = currentMoveString[3] - ONE;
6910             promoChar = NULLCHAR;
6911             break;
6912           case AmbiguousMove:
6913             /* bug? */
6914             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6915   if (appData.debugMode) {
6916     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6917     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6918     setbuf(debugFP, NULL);
6919   }
6920             DisplayError(buf, 0);
6921             return;
6922           case ImpossibleMove:
6923             /* bug? */
6924             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6925   if (appData.debugMode) {
6926     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6927     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6928     setbuf(debugFP, NULL);
6929   }
6930             DisplayError(buf, 0);
6931             return;
6932           case (ChessMove) 0:   /* end of file */
6933             if (boardIndex < backwardMostMove) {
6934                 /* Oops, gap.  How did that happen? */
6935                 DisplayError(_("Gap in move list"), 0);
6936                 return;
6937             }
6938             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6939             if (boardIndex > forwardMostMove) {
6940                 forwardMostMove = boardIndex;
6941             }
6942             return;
6943           case ElapsedTime:
6944             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6945                 strcat(parseList[boardIndex-1], " ");
6946                 strcat(parseList[boardIndex-1], yy_text);
6947             }
6948             continue;
6949           case Comment:
6950           case PGNTag:
6951           case NAG:
6952           default:
6953             /* ignore */
6954             continue;
6955           case WhiteWins:
6956           case BlackWins:
6957           case GameIsDrawn:
6958           case GameUnfinished:
6959             if (gameMode == IcsExamining) {
6960                 if (boardIndex < backwardMostMove) {
6961                     /* Oops, gap.  How did that happen? */
6962                     return;
6963                 }
6964                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6965                 return;
6966             }
6967             gameInfo.result = moveType;
6968             p = strchr(yy_text, '{');
6969             if (p == NULL) p = strchr(yy_text, '(');
6970             if (p == NULL) {
6971                 p = yy_text;
6972                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6973             } else {
6974                 q = strchr(p, *p == '{' ? '}' : ')');
6975                 if (q != NULL) *q = NULLCHAR;
6976                 p++;
6977             }
6978             gameInfo.resultDetails = StrSave(p);
6979             continue;
6980         }
6981         if (boardIndex >= forwardMostMove &&
6982             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6983             backwardMostMove = blackPlaysFirst ? 1 : 0;
6984             return;
6985         }
6986         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6987                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6988                                  parseList[boardIndex]);
6989         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6990         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6991         /* currentMoveString is set as a side-effect of yylex */
6992         strcpy(moveList[boardIndex], currentMoveString);
6993         strcat(moveList[boardIndex], "\n");
6994         boardIndex++;
6995         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
6996                                         castlingRights[boardIndex], &epStatus[boardIndex]);
6997         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
6998                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
6999           case MT_NONE:
7000           case MT_STALEMATE:
7001           default:
7002             break;
7003           case MT_CHECK:
7004             if(gameInfo.variant != VariantShogi)
7005                 strcat(parseList[boardIndex - 1], "+");
7006             break;
7007           case MT_CHECKMATE:
7008           case MT_STAINMATE:
7009             strcat(parseList[boardIndex - 1], "#");
7010             break;
7011         }
7012     }
7013 }
7014
7015
7016 /* Apply a move to the given board  */
7017 void
7018 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7019      int fromX, fromY, toX, toY;
7020      int promoChar;
7021      Board board;
7022      char *castling;
7023      char *ep;
7024 {
7025   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7026
7027     /* [HGM] compute & store e.p. status and castling rights for new position */
7028     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7029     { int i;
7030
7031       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7032       oldEP = *ep;
7033       *ep = EP_NONE;
7034
7035       if( board[toY][toX] != EmptySquare ) 
7036            *ep = EP_CAPTURE;  
7037
7038       if( board[fromY][fromX] == WhitePawn ) {
7039            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7040                *ep = EP_PAWN_MOVE;
7041            if( toY-fromY==2) {
7042                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7043                         gameInfo.variant != VariantBerolina || toX < fromX)
7044                       *ep = toX | berolina;
7045                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7046                         gameInfo.variant != VariantBerolina || toX > fromX) 
7047                       *ep = toX;
7048            }
7049       } else 
7050       if( board[fromY][fromX] == BlackPawn ) {
7051            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7052                *ep = EP_PAWN_MOVE; 
7053            if( toY-fromY== -2) {
7054                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7055                         gameInfo.variant != VariantBerolina || toX < fromX)
7056                       *ep = toX | berolina;
7057                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7058                         gameInfo.variant != VariantBerolina || toX > fromX) 
7059                       *ep = toX;
7060            }
7061        }
7062
7063        for(i=0; i<nrCastlingRights; i++) {
7064            if(castling[i] == fromX && castlingRank[i] == fromY ||
7065               castling[i] == toX   && castlingRank[i] == toY   
7066              ) castling[i] = -1; // revoke for moved or captured piece
7067        }
7068
7069     }
7070
7071   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7072   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7073        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7074          
7075   if (fromX == toX && fromY == toY) return;
7076
7077   if (fromY == DROP_RANK) {
7078         /* must be first */
7079         piece = board[toY][toX] = (ChessSquare) fromX;
7080   } else {
7081      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7082      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7083      if(gameInfo.variant == VariantKnightmate)
7084          king += (int) WhiteUnicorn - (int) WhiteKing;
7085
7086     /* Code added by Tord: */
7087     /* FRC castling assumed when king captures friendly rook. */
7088     if (board[fromY][fromX] == WhiteKing &&
7089              board[toY][toX] == WhiteRook) {
7090       board[fromY][fromX] = EmptySquare;
7091       board[toY][toX] = EmptySquare;
7092       if(toX > fromX) {
7093         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7094       } else {
7095         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7096       }
7097     } else if (board[fromY][fromX] == BlackKing &&
7098                board[toY][toX] == BlackRook) {
7099       board[fromY][fromX] = EmptySquare;
7100       board[toY][toX] = EmptySquare;
7101       if(toX > fromX) {
7102         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7103       } else {
7104         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7105       }
7106     /* End of code added by Tord */
7107
7108     } else if (board[fromY][fromX] == king
7109         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7110         && toY == fromY && toX > fromX+1) {
7111         board[fromY][fromX] = EmptySquare;
7112         board[toY][toX] = king;
7113         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7114         board[fromY][BOARD_RGHT-1] = EmptySquare;
7115     } else if (board[fromY][fromX] == king
7116         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7117                && toY == fromY && toX < fromX-1) {
7118         board[fromY][fromX] = EmptySquare;
7119         board[toY][toX] = king;
7120         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7121         board[fromY][BOARD_LEFT] = EmptySquare;
7122     } else if (board[fromY][fromX] == WhitePawn
7123                && toY == BOARD_HEIGHT-1
7124                && gameInfo.variant != VariantXiangqi
7125                ) {
7126         /* white pawn promotion */
7127         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7128         if (board[toY][toX] == EmptySquare) {
7129             board[toY][toX] = WhiteQueen;
7130         }
7131         if(gameInfo.variant==VariantBughouse ||
7132            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7133             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7134         board[fromY][fromX] = EmptySquare;
7135     } else if ((fromY == BOARD_HEIGHT-4)
7136                && (toX != fromX)
7137                && gameInfo.variant != VariantXiangqi
7138                && gameInfo.variant != VariantBerolina
7139                && (board[fromY][fromX] == WhitePawn)
7140                && (board[toY][toX] == EmptySquare)) {
7141         board[fromY][fromX] = EmptySquare;
7142         board[toY][toX] = WhitePawn;
7143         captured = board[toY - 1][toX];
7144         board[toY - 1][toX] = EmptySquare;
7145     } else if ((fromY == BOARD_HEIGHT-4)
7146                && (toX == fromX)
7147                && gameInfo.variant == VariantBerolina
7148                && (board[fromY][fromX] == WhitePawn)
7149                && (board[toY][toX] == EmptySquare)) {
7150         board[fromY][fromX] = EmptySquare;
7151         board[toY][toX] = WhitePawn;
7152         if(oldEP & EP_BEROLIN_A) {
7153                 captured = board[fromY][fromX-1];
7154                 board[fromY][fromX-1] = EmptySquare;
7155         }else{  captured = board[fromY][fromX+1];
7156                 board[fromY][fromX+1] = EmptySquare;
7157         }
7158     } else if (board[fromY][fromX] == king
7159         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7160                && toY == fromY && toX > fromX+1) {
7161         board[fromY][fromX] = EmptySquare;
7162         board[toY][toX] = king;
7163         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7164         board[fromY][BOARD_RGHT-1] = EmptySquare;
7165     } else if (board[fromY][fromX] == king
7166         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7167                && toY == fromY && toX < fromX-1) {
7168         board[fromY][fromX] = EmptySquare;
7169         board[toY][toX] = king;
7170         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7171         board[fromY][BOARD_LEFT] = EmptySquare;
7172     } else if (fromY == 7 && fromX == 3
7173                && board[fromY][fromX] == BlackKing
7174                && toY == 7 && toX == 5) {
7175         board[fromY][fromX] = EmptySquare;
7176         board[toY][toX] = BlackKing;
7177         board[fromY][7] = EmptySquare;
7178         board[toY][4] = BlackRook;
7179     } else if (fromY == 7 && fromX == 3
7180                && board[fromY][fromX] == BlackKing
7181                && toY == 7 && toX == 1) {
7182         board[fromY][fromX] = EmptySquare;
7183         board[toY][toX] = BlackKing;
7184         board[fromY][0] = EmptySquare;
7185         board[toY][2] = BlackRook;
7186     } else if (board[fromY][fromX] == BlackPawn
7187                && toY == 0
7188                && gameInfo.variant != VariantXiangqi
7189                ) {
7190         /* black pawn promotion */
7191         board[0][toX] = CharToPiece(ToLower(promoChar));
7192         if (board[0][toX] == EmptySquare) {
7193             board[0][toX] = BlackQueen;
7194         }
7195         if(gameInfo.variant==VariantBughouse ||
7196            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7197             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7198         board[fromY][fromX] = EmptySquare;
7199     } else if ((fromY == 3)
7200                && (toX != fromX)
7201                && gameInfo.variant != VariantXiangqi
7202                && gameInfo.variant != VariantBerolina
7203                && (board[fromY][fromX] == BlackPawn)
7204                && (board[toY][toX] == EmptySquare)) {
7205         board[fromY][fromX] = EmptySquare;
7206         board[toY][toX] = BlackPawn;
7207         captured = board[toY + 1][toX];
7208         board[toY + 1][toX] = EmptySquare;
7209     } else if ((fromY == 3)
7210                && (toX == fromX)
7211                && gameInfo.variant == VariantBerolina
7212                && (board[fromY][fromX] == BlackPawn)
7213                && (board[toY][toX] == EmptySquare)) {
7214         board[fromY][fromX] = EmptySquare;
7215         board[toY][toX] = BlackPawn;
7216         if(oldEP & EP_BEROLIN_A) {
7217                 captured = board[fromY][fromX-1];
7218                 board[fromY][fromX-1] = EmptySquare;
7219         }else{  captured = board[fromY][fromX+1];
7220                 board[fromY][fromX+1] = EmptySquare;
7221         }
7222     } else {
7223         board[toY][toX] = board[fromY][fromX];
7224         board[fromY][fromX] = EmptySquare;
7225     }
7226
7227     /* [HGM] now we promote for Shogi, if needed */
7228     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7229         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7230   }
7231
7232     if (gameInfo.holdingsWidth != 0) {
7233
7234       /* !!A lot more code needs to be written to support holdings  */
7235       /* [HGM] OK, so I have written it. Holdings are stored in the */
7236       /* penultimate board files, so they are automaticlly stored   */
7237       /* in the game history.                                       */
7238       if (fromY == DROP_RANK) {
7239         /* Delete from holdings, by decreasing count */
7240         /* and erasing image if necessary            */
7241         p = (int) fromX;
7242         if(p < (int) BlackPawn) { /* white drop */
7243              p -= (int)WhitePawn;
7244              if(p >= gameInfo.holdingsSize) p = 0;
7245              if(--board[p][BOARD_WIDTH-2] == 0)
7246                   board[p][BOARD_WIDTH-1] = EmptySquare;
7247         } else {                  /* black drop */
7248              p -= (int)BlackPawn;
7249              if(p >= gameInfo.holdingsSize) p = 0;
7250              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7251                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7252         }
7253       }
7254       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7255           && gameInfo.variant != VariantBughouse        ) {
7256         /* [HGM] holdings: Add to holdings, if holdings exist */
7257         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7258                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7259                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7260         }
7261         p = (int) captured;
7262         if (p >= (int) BlackPawn) {
7263           p -= (int)BlackPawn;
7264           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7265                   /* in Shogi restore piece to its original  first */
7266                   captured = (ChessSquare) (DEMOTED captured);
7267                   p = DEMOTED p;
7268           }
7269           p = PieceToNumber((ChessSquare)p);
7270           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7271           board[p][BOARD_WIDTH-2]++;
7272           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7273         } else {
7274           p -= (int)WhitePawn;
7275           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7276                   captured = (ChessSquare) (DEMOTED captured);
7277                   p = DEMOTED p;
7278           }
7279           p = PieceToNumber((ChessSquare)p);
7280           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7281           board[BOARD_HEIGHT-1-p][1]++;
7282           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7283         }
7284       }
7285
7286     } else if (gameInfo.variant == VariantAtomic) {
7287       if (captured != EmptySquare) {
7288         int y, x;
7289         for (y = toY-1; y <= toY+1; y++) {
7290           for (x = toX-1; x <= toX+1; x++) {
7291             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7292                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7293               board[y][x] = EmptySquare;
7294             }
7295           }
7296         }
7297         board[toY][toX] = EmptySquare;
7298       }
7299     }
7300     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7301         /* [HGM] Shogi promotions */
7302         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7303     }
7304
7305     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7306                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7307         // [HGM] superchess: take promotion piece out of holdings
7308         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7309         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7310             if(!--board[k][BOARD_WIDTH-2])
7311                 board[k][BOARD_WIDTH-1] = EmptySquare;
7312         } else {
7313             if(!--board[BOARD_HEIGHT-1-k][1])
7314                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7315         }
7316     }
7317
7318 }
7319
7320 /* Updates forwardMostMove */
7321 void
7322 MakeMove(fromX, fromY, toX, toY, promoChar)
7323      int fromX, fromY, toX, toY;
7324      int promoChar;
7325 {
7326 //    forwardMostMove++; // [HGM] bare: moved downstream
7327
7328     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7329         int timeLeft; static int lastLoadFlag=0; int king, piece;
7330         piece = boards[forwardMostMove][fromY][fromX];
7331         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7332         if(gameInfo.variant == VariantKnightmate)
7333             king += (int) WhiteUnicorn - (int) WhiteKing;
7334         if(forwardMostMove == 0) {
7335             if(blackPlaysFirst) 
7336                 fprintf(serverMoves, "%s;", second.tidy);
7337             fprintf(serverMoves, "%s;", first.tidy);
7338             if(!blackPlaysFirst) 
7339                 fprintf(serverMoves, "%s;", second.tidy);
7340         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7341         lastLoadFlag = loadFlag;
7342         // print base move
7343         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7344         // print castling suffix
7345         if( toY == fromY && piece == king ) {
7346             if(toX-fromX > 1)
7347                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7348             if(fromX-toX >1)
7349                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7350         }
7351         // e.p. suffix
7352         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7353              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7354              boards[forwardMostMove][toY][toX] == EmptySquare
7355              && fromX != toX )
7356                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7357         // promotion suffix
7358         if(promoChar != NULLCHAR)
7359                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7360         if(!loadFlag) {
7361             fprintf(serverMoves, "/%d/%d",
7362                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7363             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7364             else                      timeLeft = blackTimeRemaining/1000;
7365             fprintf(serverMoves, "/%d", timeLeft);
7366         }
7367         fflush(serverMoves);
7368     }
7369
7370     if (forwardMostMove+1 >= MAX_MOVES) {
7371       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7372                         0, 1);
7373       return;
7374     }
7375     if (commentList[forwardMostMove+1] != NULL) {
7376         free(commentList[forwardMostMove+1]);
7377         commentList[forwardMostMove+1] = NULL;
7378     }
7379     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7380     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7381     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7382                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7383     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7384     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7385     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7386     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7387     gameInfo.result = GameUnfinished;
7388     if (gameInfo.resultDetails != NULL) {
7389         free(gameInfo.resultDetails);
7390         gameInfo.resultDetails = NULL;
7391     }
7392     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7393                               moveList[forwardMostMove - 1]);
7394     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7395                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7396                              fromY, fromX, toY, toX, promoChar,
7397                              parseList[forwardMostMove - 1]);
7398     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7399                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7400                             castlingRights[forwardMostMove]) ) {
7401       case MT_NONE:
7402       case MT_STALEMATE:
7403       default:
7404         break;
7405       case MT_CHECK:
7406         if(gameInfo.variant != VariantShogi)
7407             strcat(parseList[forwardMostMove - 1], "+");
7408         break;
7409       case MT_CHECKMATE:
7410       case MT_STAINMATE:
7411         strcat(parseList[forwardMostMove - 1], "#");
7412         break;
7413     }
7414     if (appData.debugMode) {
7415         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7416     }
7417
7418 }
7419
7420 /* Updates currentMove if not pausing */
7421 void
7422 ShowMove(fromX, fromY, toX, toY)
7423 {
7424     int instant = (gameMode == PlayFromGameFile) ?
7425         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7426     if(appData.noGUI) return;
7427     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7428         if (!instant) {
7429             if (forwardMostMove == currentMove + 1) {
7430                 AnimateMove(boards[forwardMostMove - 1],
7431                             fromX, fromY, toX, toY);
7432             }
7433             if (appData.highlightLastMove) {
7434                 SetHighlights(fromX, fromY, toX, toY);
7435             }
7436         }
7437         currentMove = forwardMostMove;
7438     }
7439
7440     if (instant) return;
7441
7442     DisplayMove(currentMove - 1);
7443     DrawPosition(FALSE, boards[currentMove]);
7444     DisplayBothClocks();
7445     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7446 }
7447
7448 void SendEgtPath(ChessProgramState *cps)
7449 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7450         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7451
7452         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7453
7454         while(*p) {
7455             char c, *q = name+1, *r, *s;
7456
7457             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7458             while(*p && *p != ',') *q++ = *p++;
7459             *q++ = ':'; *q = 0;
7460             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7461                 strcmp(name, ",nalimov:") == 0 ) {
7462                 // take nalimov path from the menu-changeable option first, if it is defined
7463                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7464                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7465             } else
7466             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7467                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7468                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7469                 s = r = StrStr(s, ":") + 1; // beginning of path info
7470                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7471                 c = *r; *r = 0;             // temporarily null-terminate path info
7472                     *--q = 0;               // strip of trailig ':' from name
7473                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7474                 *r = c;
7475                 SendToProgram(buf,cps);     // send egtbpath command for this format
7476             }
7477             if(*p == ',') p++; // read away comma to position for next format name
7478         }
7479 }
7480
7481 void
7482 InitChessProgram(cps, setup)
7483      ChessProgramState *cps;
7484      int setup; /* [HGM] needed to setup FRC opening position */
7485 {
7486     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7487     if (appData.noChessProgram) return;
7488     hintRequested = FALSE;
7489     bookRequested = FALSE;
7490
7491     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7492     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7493     if(cps->memSize) { /* [HGM] memory */
7494         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7495         SendToProgram(buf, cps);
7496     }
7497     SendEgtPath(cps); /* [HGM] EGT */
7498     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7499         sprintf(buf, "cores %d\n", appData.smpCores);
7500         SendToProgram(buf, cps);
7501     }
7502
7503     SendToProgram(cps->initString, cps);
7504     if (gameInfo.variant != VariantNormal &&
7505         gameInfo.variant != VariantLoadable
7506         /* [HGM] also send variant if board size non-standard */
7507         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7508                                             ) {
7509       char *v = VariantName(gameInfo.variant);
7510       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7511         /* [HGM] in protocol 1 we have to assume all variants valid */
7512         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7513         DisplayFatalError(buf, 0, 1);
7514         return;
7515       }
7516
7517       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7518       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7519       if( gameInfo.variant == VariantXiangqi )
7520            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7521       if( gameInfo.variant == VariantShogi )
7522            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7523       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7524            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7525       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7526                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7527            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7528       if( gameInfo.variant == VariantCourier )
7529            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7530       if( gameInfo.variant == VariantSuper )
7531            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7532       if( gameInfo.variant == VariantGreat )
7533            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7534
7535       if(overruled) {
7536            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7537                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7538            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7539            if(StrStr(cps->variants, b) == NULL) { 
7540                // specific sized variant not known, check if general sizing allowed
7541                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7542                    if(StrStr(cps->variants, "boardsize") == NULL) {
7543                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7544                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7545                        DisplayFatalError(buf, 0, 1);
7546                        return;
7547                    }
7548                    /* [HGM] here we really should compare with the maximum supported board size */
7549                }
7550            }
7551       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7552       sprintf(buf, "variant %s\n", b);
7553       SendToProgram(buf, cps);
7554     }
7555     currentlyInitializedVariant = gameInfo.variant;
7556
7557     /* [HGM] send opening position in FRC to first engine */
7558     if(setup) {
7559           SendToProgram("force\n", cps);
7560           SendBoard(cps, 0);
7561           /* engine is now in force mode! Set flag to wake it up after first move. */
7562           setboardSpoiledMachineBlack = 1;
7563     }
7564
7565     if (cps->sendICS) {
7566       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7567       SendToProgram(buf, cps);
7568     }
7569     cps->maybeThinking = FALSE;
7570     cps->offeredDraw = 0;
7571     if (!appData.icsActive) {
7572         SendTimeControl(cps, movesPerSession, timeControl,
7573                         timeIncrement, appData.searchDepth,
7574                         searchTime);
7575     }
7576     if (appData.showThinking 
7577         // [HGM] thinking: four options require thinking output to be sent
7578         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7579                                 ) {
7580         SendToProgram("post\n", cps);
7581     }
7582     SendToProgram("hard\n", cps);
7583     if (!appData.ponderNextMove) {
7584         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7585            it without being sure what state we are in first.  "hard"
7586            is not a toggle, so that one is OK.
7587          */
7588         SendToProgram("easy\n", cps);
7589     }
7590     if (cps->usePing) {
7591       sprintf(buf, "ping %d\n", ++cps->lastPing);
7592       SendToProgram(buf, cps);
7593     }
7594     cps->initDone = TRUE;
7595 }   
7596
7597
7598 void
7599 StartChessProgram(cps)
7600      ChessProgramState *cps;
7601 {
7602     char buf[MSG_SIZ];
7603     int err;
7604
7605     if (appData.noChessProgram) return;
7606     cps->initDone = FALSE;
7607
7608     if (strcmp(cps->host, "localhost") == 0) {
7609         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7610     } else if (*appData.remoteShell == NULLCHAR) {
7611         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7612     } else {
7613         if (*appData.remoteUser == NULLCHAR) {
7614           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7615                     cps->program);
7616         } else {
7617           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7618                     cps->host, appData.remoteUser, cps->program);
7619         }
7620         err = StartChildProcess(buf, "", &cps->pr);
7621     }
7622     
7623     if (err != 0) {
7624         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7625         DisplayFatalError(buf, err, 1);
7626         cps->pr = NoProc;
7627         cps->isr = NULL;
7628         return;
7629     }
7630     
7631     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7632     if (cps->protocolVersion > 1) {
7633       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7634       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7635       cps->comboCnt = 0;  //                and values of combo boxes
7636       SendToProgram(buf, cps);
7637     } else {
7638       SendToProgram("xboard\n", cps);
7639     }
7640 }
7641
7642
7643 void
7644 TwoMachinesEventIfReady P((void))
7645 {
7646   if (first.lastPing != first.lastPong) {
7647     DisplayMessage("", _("Waiting for first chess program"));
7648     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7649     return;
7650   }
7651   if (second.lastPing != second.lastPong) {
7652     DisplayMessage("", _("Waiting for second chess program"));
7653     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7654     return;
7655   }
7656   ThawUI();
7657   TwoMachinesEvent();
7658 }
7659
7660 void
7661 NextMatchGame P((void))
7662 {
7663     int index; /* [HGM] autoinc: step lod index during match */
7664     Reset(FALSE, TRUE);
7665     if (*appData.loadGameFile != NULLCHAR) {
7666         index = appData.loadGameIndex;
7667         if(index < 0) { // [HGM] autoinc
7668             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7669             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7670         } 
7671         LoadGameFromFile(appData.loadGameFile,
7672                          index,
7673                          appData.loadGameFile, FALSE);
7674     } else if (*appData.loadPositionFile != NULLCHAR) {
7675         index = appData.loadPositionIndex;
7676         if(index < 0) { // [HGM] autoinc
7677             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7678             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7679         } 
7680         LoadPositionFromFile(appData.loadPositionFile,
7681                              index,
7682                              appData.loadPositionFile);
7683     }
7684     TwoMachinesEventIfReady();
7685 }
7686
7687 void UserAdjudicationEvent( int result )
7688 {
7689     ChessMove gameResult = GameIsDrawn;
7690
7691     if( result > 0 ) {
7692         gameResult = WhiteWins;
7693     }
7694     else if( result < 0 ) {
7695         gameResult = BlackWins;
7696     }
7697
7698     if( gameMode == TwoMachinesPlay ) {
7699         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7700     }
7701 }
7702
7703
7704 // [HGM] save: calculate checksum of game to make games easily identifiable
7705 int StringCheckSum(char *s)
7706 {
7707         int i = 0;
7708         if(s==NULL) return 0;
7709         while(*s) i = i*259 + *s++;
7710         return i;
7711 }
7712
7713 int GameCheckSum()
7714 {
7715         int i, sum=0;
7716         for(i=backwardMostMove; i<forwardMostMove; i++) {
7717                 sum += pvInfoList[i].depth;
7718                 sum += StringCheckSum(parseList[i]);
7719                 sum += StringCheckSum(commentList[i]);
7720                 sum *= 261;
7721         }
7722         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7723         return sum + StringCheckSum(commentList[i]);
7724 } // end of save patch
7725
7726 void
7727 GameEnds(result, resultDetails, whosays)
7728      ChessMove result;
7729      char *resultDetails;
7730      int whosays;
7731 {
7732     GameMode nextGameMode;
7733     int isIcsGame;
7734     char buf[MSG_SIZ];
7735
7736     if(endingGame) return; /* [HGM] crash: forbid recursion */
7737     endingGame = 1;
7738
7739     if (appData.debugMode) {
7740       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7741               result, resultDetails ? resultDetails : "(null)", whosays);
7742     }
7743
7744     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7745         /* If we are playing on ICS, the server decides when the
7746            game is over, but the engine can offer to draw, claim 
7747            a draw, or resign. 
7748          */
7749 #if ZIPPY
7750         if (appData.zippyPlay && first.initDone) {
7751             if (result == GameIsDrawn) {
7752                 /* In case draw still needs to be claimed */
7753                 SendToICS(ics_prefix);
7754                 SendToICS("draw\n");
7755             } else if (StrCaseStr(resultDetails, "resign")) {
7756                 SendToICS(ics_prefix);
7757                 SendToICS("resign\n");
7758             }
7759         }
7760 #endif
7761         endingGame = 0; /* [HGM] crash */
7762         return;
7763     }
7764
7765     /* If we're loading the game from a file, stop */
7766     if (whosays == GE_FILE) {
7767       (void) StopLoadGameTimer();
7768       gameFileFP = NULL;
7769     }
7770
7771     /* Cancel draw offers */
7772     first.offeredDraw = second.offeredDraw = 0;
7773
7774     /* If this is an ICS game, only ICS can really say it's done;
7775        if not, anyone can. */
7776     isIcsGame = (gameMode == IcsPlayingWhite || 
7777                  gameMode == IcsPlayingBlack || 
7778                  gameMode == IcsObserving    || 
7779                  gameMode == IcsExamining);
7780
7781     if (!isIcsGame || whosays == GE_ICS) {
7782         /* OK -- not an ICS game, or ICS said it was done */
7783         StopClocks();
7784         if (!isIcsGame && !appData.noChessProgram) 
7785           SetUserThinkingEnables();
7786     
7787         /* [HGM] if a machine claims the game end we verify this claim */
7788         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7789             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7790                 char claimer;
7791                 ChessMove trueResult = (ChessMove) -1;
7792
7793                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7794                                             first.twoMachinesColor[0] :
7795                                             second.twoMachinesColor[0] ;
7796
7797                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7798                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7799                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7800                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7801                 } else
7802                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7803                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7804                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7805                 } else
7806                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7807                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7808                 }
7809
7810                 // now verify win claims, but not in drop games, as we don't understand those yet
7811                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7812                                                  || gameInfo.variant == VariantGreat) &&
7813                     (result == WhiteWins && claimer == 'w' ||
7814                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7815                       if (appData.debugMode) {
7816                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7817                                 result, epStatus[forwardMostMove], forwardMostMove);
7818                       }
7819                       if(result != trueResult) {
7820                               sprintf(buf, "False win claim: '%s'", resultDetails);
7821                               result = claimer == 'w' ? BlackWins : WhiteWins;
7822                               resultDetails = buf;
7823                       }
7824                 } else
7825                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7826                     && (forwardMostMove <= backwardMostMove ||
7827                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7828                         (claimer=='b')==(forwardMostMove&1))
7829                                                                                   ) {
7830                       /* [HGM] verify: draws that were not flagged are false claims */
7831                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7832                       result = claimer == 'w' ? BlackWins : WhiteWins;
7833                       resultDetails = buf;
7834                 }
7835                 /* (Claiming a loss is accepted no questions asked!) */
7836             }
7837             /* [HGM] bare: don't allow bare King to win */
7838             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7839                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7840                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7841                && result != GameIsDrawn)
7842             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7843                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7844                         int p = (int)boards[forwardMostMove][i][j] - color;
7845                         if(p >= 0 && p <= (int)WhiteKing) k++;
7846                 }
7847                 if (appData.debugMode) {
7848                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7849                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7850                 }
7851                 if(k <= 1) {
7852                         result = GameIsDrawn;
7853                         sprintf(buf, "%s but bare king", resultDetails);
7854                         resultDetails = buf;
7855                 }
7856             }
7857         }
7858
7859
7860         if(serverMoves != NULL && !loadFlag) { char c = '=';
7861             if(result==WhiteWins) c = '+';
7862             if(result==BlackWins) c = '-';
7863             if(resultDetails != NULL)
7864                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7865         }
7866         if (resultDetails != NULL) {
7867             gameInfo.result = result;
7868             gameInfo.resultDetails = StrSave(resultDetails);
7869
7870             /* display last move only if game was not loaded from file */
7871             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7872                 DisplayMove(currentMove - 1);
7873     
7874             if (forwardMostMove != 0) {
7875                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7876                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7877                                                                 ) {
7878                     if (*appData.saveGameFile != NULLCHAR) {
7879                         SaveGameToFile(appData.saveGameFile, TRUE);
7880                     } else if (appData.autoSaveGames) {
7881                         AutoSaveGame();
7882                     }
7883                     if (*appData.savePositionFile != NULLCHAR) {
7884                         SavePositionToFile(appData.savePositionFile);
7885                     }
7886                 }
7887             }
7888
7889             /* Tell program how game ended in case it is learning */
7890             /* [HGM] Moved this to after saving the PGN, just in case */
7891             /* engine died and we got here through time loss. In that */
7892             /* case we will get a fatal error writing the pipe, which */
7893             /* would otherwise lose us the PGN.                       */
7894             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7895             /* output during GameEnds should never be fatal anymore   */
7896             if (gameMode == MachinePlaysWhite ||
7897                 gameMode == MachinePlaysBlack ||
7898                 gameMode == TwoMachinesPlay ||
7899                 gameMode == IcsPlayingWhite ||
7900                 gameMode == IcsPlayingBlack ||
7901                 gameMode == BeginningOfGame) {
7902                 char buf[MSG_SIZ];
7903                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7904                         resultDetails);
7905                 if (first.pr != NoProc) {
7906                     SendToProgram(buf, &first);
7907                 }
7908                 if (second.pr != NoProc &&
7909                     gameMode == TwoMachinesPlay) {
7910                     SendToProgram(buf, &second);
7911                 }
7912             }
7913         }
7914
7915         if (appData.icsActive) {
7916             if (appData.quietPlay &&
7917                 (gameMode == IcsPlayingWhite ||
7918                  gameMode == IcsPlayingBlack)) {
7919                 SendToICS(ics_prefix);
7920                 SendToICS("set shout 1\n");
7921             }
7922             nextGameMode = IcsIdle;
7923             ics_user_moved = FALSE;
7924             /* clean up premove.  It's ugly when the game has ended and the
7925              * premove highlights are still on the board.
7926              */
7927             if (gotPremove) {
7928               gotPremove = FALSE;
7929               ClearPremoveHighlights();
7930               DrawPosition(FALSE, boards[currentMove]);
7931             }
7932             if (whosays == GE_ICS) {
7933                 switch (result) {
7934                 case WhiteWins:
7935                     if (gameMode == IcsPlayingWhite)
7936                         PlayIcsWinSound();
7937                     else if(gameMode == IcsPlayingBlack)
7938                         PlayIcsLossSound();
7939                     break;
7940                 case BlackWins:
7941                     if (gameMode == IcsPlayingBlack)
7942                         PlayIcsWinSound();
7943                     else if(gameMode == IcsPlayingWhite)
7944                         PlayIcsLossSound();
7945                     break;
7946                 case GameIsDrawn:
7947                     PlayIcsDrawSound();
7948                     break;
7949                 default:
7950                     PlayIcsUnfinishedSound();
7951                 }
7952             }
7953         } else if (gameMode == EditGame ||
7954                    gameMode == PlayFromGameFile || 
7955                    gameMode == AnalyzeMode || 
7956                    gameMode == AnalyzeFile) {
7957             nextGameMode = gameMode;
7958         } else {
7959             nextGameMode = EndOfGame;
7960         }
7961         pausing = FALSE;
7962         ModeHighlight();
7963     } else {
7964         nextGameMode = gameMode;
7965     }
7966
7967     if (appData.noChessProgram) {
7968         gameMode = nextGameMode;
7969         ModeHighlight();
7970         endingGame = 0; /* [HGM] crash */
7971         return;
7972     }
7973
7974     if (first.reuse) {
7975         /* Put first chess program into idle state */
7976         if (first.pr != NoProc &&
7977             (gameMode == MachinePlaysWhite ||
7978              gameMode == MachinePlaysBlack ||
7979              gameMode == TwoMachinesPlay ||
7980              gameMode == IcsPlayingWhite ||
7981              gameMode == IcsPlayingBlack ||
7982              gameMode == BeginningOfGame)) {
7983             SendToProgram("force\n", &first);
7984             if (first.usePing) {
7985               char buf[MSG_SIZ];
7986               sprintf(buf, "ping %d\n", ++first.lastPing);
7987               SendToProgram(buf, &first);
7988             }
7989         }
7990     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7991         /* Kill off first chess program */
7992         if (first.isr != NULL)
7993           RemoveInputSource(first.isr);
7994         first.isr = NULL;
7995     
7996         if (first.pr != NoProc) {
7997             ExitAnalyzeMode();
7998             DoSleep( appData.delayBeforeQuit );
7999             SendToProgram("quit\n", &first);
8000             DoSleep( appData.delayAfterQuit );
8001             DestroyChildProcess(first.pr, first.useSigterm);
8002         }
8003         first.pr = NoProc;
8004     }
8005     if (second.reuse) {
8006         /* Put second chess program into idle state */
8007         if (second.pr != NoProc &&
8008             gameMode == TwoMachinesPlay) {
8009             SendToProgram("force\n", &second);
8010             if (second.usePing) {
8011               char buf[MSG_SIZ];
8012               sprintf(buf, "ping %d\n", ++second.lastPing);
8013               SendToProgram(buf, &second);
8014             }
8015         }
8016     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8017         /* Kill off second chess program */
8018         if (second.isr != NULL)
8019           RemoveInputSource(second.isr);
8020         second.isr = NULL;
8021     
8022         if (second.pr != NoProc) {
8023             DoSleep( appData.delayBeforeQuit );
8024             SendToProgram("quit\n", &second);
8025             DoSleep( appData.delayAfterQuit );
8026             DestroyChildProcess(second.pr, second.useSigterm);
8027         }
8028         second.pr = NoProc;
8029     }
8030
8031     if (matchMode && gameMode == TwoMachinesPlay) {
8032         switch (result) {
8033         case WhiteWins:
8034           if (first.twoMachinesColor[0] == 'w') {
8035             first.matchWins++;
8036           } else {
8037             second.matchWins++;
8038           }
8039           break;
8040         case BlackWins:
8041           if (first.twoMachinesColor[0] == 'b') {
8042             first.matchWins++;
8043           } else {
8044             second.matchWins++;
8045           }
8046           break;
8047         default:
8048           break;
8049         }
8050         if (matchGame < appData.matchGames) {
8051             char *tmp;
8052             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8053                 tmp = first.twoMachinesColor;
8054                 first.twoMachinesColor = second.twoMachinesColor;
8055                 second.twoMachinesColor = tmp;
8056             }
8057             gameMode = nextGameMode;
8058             matchGame++;
8059             if(appData.matchPause>10000 || appData.matchPause<10)
8060                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8061             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8062             endingGame = 0; /* [HGM] crash */
8063             return;
8064         } else {
8065             char buf[MSG_SIZ];
8066             gameMode = nextGameMode;
8067             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8068                     first.tidy, second.tidy,
8069                     first.matchWins, second.matchWins,
8070                     appData.matchGames - (first.matchWins + second.matchWins));
8071             DisplayFatalError(buf, 0, 0);
8072         }
8073     }
8074     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8075         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8076       ExitAnalyzeMode();
8077     gameMode = nextGameMode;
8078     ModeHighlight();
8079     endingGame = 0;  /* [HGM] crash */
8080 }
8081
8082 /* Assumes program was just initialized (initString sent).
8083    Leaves program in force mode. */
8084 void
8085 FeedMovesToProgram(cps, upto) 
8086      ChessProgramState *cps;
8087      int upto;
8088 {
8089     int i;
8090     
8091     if (appData.debugMode)
8092       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8093               startedFromSetupPosition ? "position and " : "",
8094               backwardMostMove, upto, cps->which);
8095     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8096         // [HGM] variantswitch: make engine aware of new variant
8097         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8098                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8099         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8100         SendToProgram(buf, cps);
8101         currentlyInitializedVariant = gameInfo.variant;
8102     }
8103     SendToProgram("force\n", cps);
8104     if (startedFromSetupPosition) {
8105         SendBoard(cps, backwardMostMove);
8106     if (appData.debugMode) {
8107         fprintf(debugFP, "feedMoves\n");
8108     }
8109     }
8110     for (i = backwardMostMove; i < upto; i++) {
8111         SendMoveToProgram(i, cps);
8112     }
8113 }
8114
8115
8116 void
8117 ResurrectChessProgram()
8118 {
8119      /* The chess program may have exited.
8120         If so, restart it and feed it all the moves made so far. */
8121
8122     if (appData.noChessProgram || first.pr != NoProc) return;
8123     
8124     StartChessProgram(&first);
8125     InitChessProgram(&first, FALSE);
8126     FeedMovesToProgram(&first, currentMove);
8127
8128     if (!first.sendTime) {
8129         /* can't tell gnuchess what its clock should read,
8130            so we bow to its notion. */
8131         ResetClocks();
8132         timeRemaining[0][currentMove] = whiteTimeRemaining;
8133         timeRemaining[1][currentMove] = blackTimeRemaining;
8134     }
8135
8136     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8137                 appData.icsEngineAnalyze) && first.analysisSupport) {
8138       SendToProgram("analyze\n", &first);
8139       first.analyzing = TRUE;
8140     }
8141 }
8142
8143 /*
8144  * Button procedures
8145  */
8146 void
8147 Reset(redraw, init)
8148      int redraw, init;
8149 {
8150     int i;
8151
8152     if (appData.debugMode) {
8153         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8154                 redraw, init, gameMode);
8155     }
8156     pausing = pauseExamInvalid = FALSE;
8157     startedFromSetupPosition = blackPlaysFirst = FALSE;
8158     firstMove = TRUE;
8159     whiteFlag = blackFlag = FALSE;
8160     userOfferedDraw = FALSE;
8161     hintRequested = bookRequested = FALSE;
8162     first.maybeThinking = FALSE;
8163     second.maybeThinking = FALSE;
8164     first.bookSuspend = FALSE; // [HGM] book
8165     second.bookSuspend = FALSE;
8166     thinkOutput[0] = NULLCHAR;
8167     lastHint[0] = NULLCHAR;
8168     ClearGameInfo(&gameInfo);
8169     gameInfo.variant = StringToVariant(appData.variant);
8170     ics_user_moved = ics_clock_paused = FALSE;
8171     ics_getting_history = H_FALSE;
8172     ics_gamenum = -1;
8173     white_holding[0] = black_holding[0] = NULLCHAR;
8174     ClearProgramStats();
8175     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8176     
8177     ResetFrontEnd();
8178     ClearHighlights();
8179     flipView = appData.flipView;
8180     ClearPremoveHighlights();
8181     gotPremove = FALSE;
8182     alarmSounded = FALSE;
8183
8184     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8185     if(appData.serverMovesName != NULL) {
8186         /* [HGM] prepare to make moves file for broadcasting */
8187         clock_t t = clock();
8188         if(serverMoves != NULL) fclose(serverMoves);
8189         serverMoves = fopen(appData.serverMovesName, "r");
8190         if(serverMoves != NULL) {
8191             fclose(serverMoves);
8192             /* delay 15 sec before overwriting, so all clients can see end */
8193             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8194         }
8195         serverMoves = fopen(appData.serverMovesName, "w");
8196     }
8197
8198     ExitAnalyzeMode();
8199     gameMode = BeginningOfGame;
8200     ModeHighlight();
8201     if(appData.icsActive) gameInfo.variant = VariantNormal;
8202     currentMove = forwardMostMove = backwardMostMove = 0;
8203     InitPosition(redraw);
8204     for (i = 0; i < MAX_MOVES; i++) {
8205         if (commentList[i] != NULL) {
8206             free(commentList[i]);
8207             commentList[i] = NULL;
8208         }
8209     }
8210     ResetClocks();
8211     timeRemaining[0][0] = whiteTimeRemaining;
8212     timeRemaining[1][0] = blackTimeRemaining;
8213     if (first.pr == NULL) {
8214         StartChessProgram(&first);
8215     }
8216     if (init) {
8217             InitChessProgram(&first, startedFromSetupPosition);
8218     }
8219     DisplayTitle("");
8220     DisplayMessage("", "");
8221     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8222     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8223 }
8224
8225 void
8226 AutoPlayGameLoop()
8227 {
8228     for (;;) {
8229         if (!AutoPlayOneMove())
8230           return;
8231         if (matchMode || appData.timeDelay == 0)
8232           continue;
8233         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8234           return;
8235         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8236         break;
8237     }
8238 }
8239
8240
8241 int
8242 AutoPlayOneMove()
8243 {
8244     int fromX, fromY, toX, toY;
8245
8246     if (appData.debugMode) {
8247       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8248     }
8249
8250     if (gameMode != PlayFromGameFile)
8251       return FALSE;
8252
8253     if (currentMove >= forwardMostMove) {
8254       gameMode = EditGame;
8255       ModeHighlight();
8256
8257       /* [AS] Clear current move marker at the end of a game */
8258       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8259
8260       return FALSE;
8261     }
8262     
8263     toX = moveList[currentMove][2] - AAA;
8264     toY = moveList[currentMove][3] - ONE;
8265
8266     if (moveList[currentMove][1] == '@') {
8267         if (appData.highlightLastMove) {
8268             SetHighlights(-1, -1, toX, toY);
8269         }
8270     } else {
8271         fromX = moveList[currentMove][0] - AAA;
8272         fromY = moveList[currentMove][1] - ONE;
8273
8274         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8275
8276         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8277
8278         if (appData.highlightLastMove) {
8279             SetHighlights(fromX, fromY, toX, toY);
8280         }
8281     }
8282     DisplayMove(currentMove);
8283     SendMoveToProgram(currentMove++, &first);
8284     DisplayBothClocks();
8285     DrawPosition(FALSE, boards[currentMove]);
8286     // [HGM] PV info: always display, routine tests if empty
8287     DisplayComment(currentMove - 1, commentList[currentMove]);
8288     return TRUE;
8289 }
8290
8291
8292 int
8293 LoadGameOneMove(readAhead)
8294      ChessMove readAhead;
8295 {
8296     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8297     char promoChar = NULLCHAR;
8298     ChessMove moveType;
8299     char move[MSG_SIZ];
8300     char *p, *q;
8301     
8302     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8303         gameMode != AnalyzeMode && gameMode != Training) {
8304         gameFileFP = NULL;
8305         return FALSE;
8306     }
8307     
8308     yyboardindex = forwardMostMove;
8309     if (readAhead != (ChessMove)0) {
8310       moveType = readAhead;
8311     } else {
8312       if (gameFileFP == NULL)
8313           return FALSE;
8314       moveType = (ChessMove) yylex();
8315     }
8316     
8317     done = FALSE;
8318     switch (moveType) {
8319       case Comment:
8320         if (appData.debugMode) 
8321           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8322         p = yy_text;
8323         if (*p == '{' || *p == '[' || *p == '(') {
8324             p[strlen(p) - 1] = NULLCHAR;
8325             p++;
8326         }
8327
8328         /* append the comment but don't display it */
8329         while (*p == '\n') p++;
8330         AppendComment(currentMove, p);
8331         return TRUE;
8332
8333       case WhiteCapturesEnPassant:
8334       case BlackCapturesEnPassant:
8335       case WhitePromotionChancellor:
8336       case BlackPromotionChancellor:
8337       case WhitePromotionArchbishop:
8338       case BlackPromotionArchbishop:
8339       case WhitePromotionCentaur:
8340       case BlackPromotionCentaur:
8341       case WhitePromotionQueen:
8342       case BlackPromotionQueen:
8343       case WhitePromotionRook:
8344       case BlackPromotionRook:
8345       case WhitePromotionBishop:
8346       case BlackPromotionBishop:
8347       case WhitePromotionKnight:
8348       case BlackPromotionKnight:
8349       case WhitePromotionKing:
8350       case BlackPromotionKing:
8351       case NormalMove:
8352       case WhiteKingSideCastle:
8353       case WhiteQueenSideCastle:
8354       case BlackKingSideCastle:
8355       case BlackQueenSideCastle:
8356       case WhiteKingSideCastleWild:
8357       case WhiteQueenSideCastleWild:
8358       case BlackKingSideCastleWild:
8359       case BlackQueenSideCastleWild:
8360       /* PUSH Fabien */
8361       case WhiteHSideCastleFR:
8362       case WhiteASideCastleFR:
8363       case BlackHSideCastleFR:
8364       case BlackASideCastleFR:
8365       /* POP Fabien */
8366         if (appData.debugMode)
8367           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8368         fromX = currentMoveString[0] - AAA;
8369         fromY = currentMoveString[1] - ONE;
8370         toX = currentMoveString[2] - AAA;
8371         toY = currentMoveString[3] - ONE;
8372         promoChar = currentMoveString[4];
8373         break;
8374
8375       case WhiteDrop:
8376       case BlackDrop:
8377         if (appData.debugMode)
8378           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8379         fromX = moveType == WhiteDrop ?
8380           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8381         (int) CharToPiece(ToLower(currentMoveString[0]));
8382         fromY = DROP_RANK;
8383         toX = currentMoveString[2] - AAA;
8384         toY = currentMoveString[3] - ONE;
8385         break;
8386
8387       case WhiteWins:
8388       case BlackWins:
8389       case GameIsDrawn:
8390       case GameUnfinished:
8391         if (appData.debugMode)
8392           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8393         p = strchr(yy_text, '{');
8394         if (p == NULL) p = strchr(yy_text, '(');
8395         if (p == NULL) {
8396             p = yy_text;
8397             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8398         } else {
8399             q = strchr(p, *p == '{' ? '}' : ')');
8400             if (q != NULL) *q = NULLCHAR;
8401             p++;
8402         }
8403         GameEnds(moveType, p, GE_FILE);
8404         done = TRUE;
8405         if (cmailMsgLoaded) {
8406             ClearHighlights();
8407             flipView = WhiteOnMove(currentMove);
8408             if (moveType == GameUnfinished) flipView = !flipView;
8409             if (appData.debugMode)
8410               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8411         }
8412         break;
8413
8414       case (ChessMove) 0:       /* end of file */
8415         if (appData.debugMode)
8416           fprintf(debugFP, "Parser hit end of file\n");
8417         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8418                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8419           case MT_NONE:
8420           case MT_CHECK:
8421             break;
8422           case MT_CHECKMATE:
8423           case MT_STAINMATE:
8424             if (WhiteOnMove(currentMove)) {
8425                 GameEnds(BlackWins, "Black mates", GE_FILE);
8426             } else {
8427                 GameEnds(WhiteWins, "White mates", GE_FILE);
8428             }
8429             break;
8430           case MT_STALEMATE:
8431             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8432             break;
8433         }
8434         done = TRUE;
8435         break;
8436
8437       case MoveNumberOne:
8438         if (lastLoadGameStart == GNUChessGame) {
8439             /* GNUChessGames have numbers, but they aren't move numbers */
8440             if (appData.debugMode)
8441               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8442                       yy_text, (int) moveType);
8443             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8444         }
8445         /* else fall thru */
8446
8447       case XBoardGame:
8448       case GNUChessGame:
8449       case PGNTag:
8450         /* Reached start of next game in file */
8451         if (appData.debugMode)
8452           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8453         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8454                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8455           case MT_NONE:
8456           case MT_CHECK:
8457             break;
8458           case MT_CHECKMATE:
8459           case MT_STAINMATE:
8460             if (WhiteOnMove(currentMove)) {
8461                 GameEnds(BlackWins, "Black mates", GE_FILE);
8462             } else {
8463                 GameEnds(WhiteWins, "White mates", GE_FILE);
8464             }
8465             break;
8466           case MT_STALEMATE:
8467             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8468             break;
8469         }
8470         done = TRUE;
8471         break;
8472
8473       case PositionDiagram:     /* should not happen; ignore */
8474       case ElapsedTime:         /* ignore */
8475       case NAG:                 /* ignore */
8476         if (appData.debugMode)
8477           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8478                   yy_text, (int) moveType);
8479         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8480
8481       case IllegalMove:
8482         if (appData.testLegality) {
8483             if (appData.debugMode)
8484               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8485             sprintf(move, _("Illegal move: %d.%s%s"),
8486                     (forwardMostMove / 2) + 1,
8487                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8488             DisplayError(move, 0);
8489             done = TRUE;
8490         } else {
8491             if (appData.debugMode)
8492               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8493                       yy_text, currentMoveString);
8494             fromX = currentMoveString[0] - AAA;
8495             fromY = currentMoveString[1] - ONE;
8496             toX = currentMoveString[2] - AAA;
8497             toY = currentMoveString[3] - ONE;
8498             promoChar = currentMoveString[4];
8499         }
8500         break;
8501
8502       case AmbiguousMove:
8503         if (appData.debugMode)
8504           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8505         sprintf(move, _("Ambiguous move: %d.%s%s"),
8506                 (forwardMostMove / 2) + 1,
8507                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8508         DisplayError(move, 0);
8509         done = TRUE;
8510         break;
8511
8512       default:
8513       case ImpossibleMove:
8514         if (appData.debugMode)
8515           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8516         sprintf(move, _("Illegal move: %d.%s%s"),
8517                 (forwardMostMove / 2) + 1,
8518                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8519         DisplayError(move, 0);
8520         done = TRUE;
8521         break;
8522     }
8523
8524     if (done) {
8525         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8526             DrawPosition(FALSE, boards[currentMove]);
8527             DisplayBothClocks();
8528             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8529               DisplayComment(currentMove - 1, commentList[currentMove]);
8530         }
8531         (void) StopLoadGameTimer();
8532         gameFileFP = NULL;
8533         cmailOldMove = forwardMostMove;
8534         return FALSE;
8535     } else {
8536         /* currentMoveString is set as a side-effect of yylex */
8537         strcat(currentMoveString, "\n");
8538         strcpy(moveList[forwardMostMove], currentMoveString);
8539         
8540         thinkOutput[0] = NULLCHAR;
8541         MakeMove(fromX, fromY, toX, toY, promoChar);
8542         currentMove = forwardMostMove;
8543         return TRUE;
8544     }
8545 }
8546
8547 /* Load the nth game from the given file */
8548 int
8549 LoadGameFromFile(filename, n, title, useList)
8550      char *filename;
8551      int n;
8552      char *title;
8553      /*Boolean*/ int useList;
8554 {
8555     FILE *f;
8556     char buf[MSG_SIZ];
8557
8558     if (strcmp(filename, "-") == 0) {
8559         f = stdin;
8560         title = "stdin";
8561     } else {
8562         f = fopen(filename, "rb");
8563         if (f == NULL) {
8564           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8565             DisplayError(buf, errno);
8566             return FALSE;
8567         }
8568     }
8569     if (fseek(f, 0, 0) == -1) {
8570         /* f is not seekable; probably a pipe */
8571         useList = FALSE;
8572     }
8573     if (useList && n == 0) {
8574         int error = GameListBuild(f);
8575         if (error) {
8576             DisplayError(_("Cannot build game list"), error);
8577         } else if (!ListEmpty(&gameList) &&
8578                    ((ListGame *) gameList.tailPred)->number > 1) {
8579             GameListPopUp(f, title);
8580             return TRUE;
8581         }
8582         GameListDestroy();
8583         n = 1;
8584     }
8585     if (n == 0) n = 1;
8586     return LoadGame(f, n, title, FALSE);
8587 }
8588
8589
8590 void
8591 MakeRegisteredMove()
8592 {
8593     int fromX, fromY, toX, toY;
8594     char promoChar;
8595     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8596         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8597           case CMAIL_MOVE:
8598           case CMAIL_DRAW:
8599             if (appData.debugMode)
8600               fprintf(debugFP, "Restoring %s for game %d\n",
8601                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8602     
8603             thinkOutput[0] = NULLCHAR;
8604             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8605             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8606             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8607             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8608             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8609             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8610             MakeMove(fromX, fromY, toX, toY, promoChar);
8611             ShowMove(fromX, fromY, toX, toY);
8612               
8613             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8614                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8615               case MT_NONE:
8616               case MT_CHECK:
8617                 break;
8618                 
8619               case MT_CHECKMATE:
8620               case MT_STAINMATE:
8621                 if (WhiteOnMove(currentMove)) {
8622                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8623                 } else {
8624                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8625                 }
8626                 break;
8627                 
8628               case MT_STALEMATE:
8629                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8630                 break;
8631             }
8632
8633             break;
8634             
8635           case CMAIL_RESIGN:
8636             if (WhiteOnMove(currentMove)) {
8637                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8638             } else {
8639                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8640             }
8641             break;
8642             
8643           case CMAIL_ACCEPT:
8644             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8645             break;
8646               
8647           default:
8648             break;
8649         }
8650     }
8651
8652     return;
8653 }
8654
8655 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8656 int
8657 CmailLoadGame(f, gameNumber, title, useList)
8658      FILE *f;
8659      int gameNumber;
8660      char *title;
8661      int useList;
8662 {
8663     int retVal;
8664
8665     if (gameNumber > nCmailGames) {
8666         DisplayError(_("No more games in this message"), 0);
8667         return FALSE;
8668     }
8669     if (f == lastLoadGameFP) {
8670         int offset = gameNumber - lastLoadGameNumber;
8671         if (offset == 0) {
8672             cmailMsg[0] = NULLCHAR;
8673             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8674                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8675                 nCmailMovesRegistered--;
8676             }
8677             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8678             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8679                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8680             }
8681         } else {
8682             if (! RegisterMove()) return FALSE;
8683         }
8684     }
8685
8686     retVal = LoadGame(f, gameNumber, title, useList);
8687
8688     /* Make move registered during previous look at this game, if any */
8689     MakeRegisteredMove();
8690
8691     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8692         commentList[currentMove]
8693           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8694         DisplayComment(currentMove - 1, commentList[currentMove]);
8695     }
8696
8697     return retVal;
8698 }
8699
8700 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8701 int
8702 ReloadGame(offset)
8703      int offset;
8704 {
8705     int gameNumber = lastLoadGameNumber + offset;
8706     if (lastLoadGameFP == NULL) {
8707         DisplayError(_("No game has been loaded yet"), 0);
8708         return FALSE;
8709     }
8710     if (gameNumber <= 0) {
8711         DisplayError(_("Can't back up any further"), 0);
8712         return FALSE;
8713     }
8714     if (cmailMsgLoaded) {
8715         return CmailLoadGame(lastLoadGameFP, gameNumber,
8716                              lastLoadGameTitle, lastLoadGameUseList);
8717     } else {
8718         return LoadGame(lastLoadGameFP, gameNumber,
8719                         lastLoadGameTitle, lastLoadGameUseList);
8720     }
8721 }
8722
8723
8724
8725 /* Load the nth game from open file f */
8726 int
8727 LoadGame(f, gameNumber, title, useList)
8728      FILE *f;
8729      int gameNumber;
8730      char *title;
8731      int useList;
8732 {
8733     ChessMove cm;
8734     char buf[MSG_SIZ];
8735     int gn = gameNumber;
8736     ListGame *lg = NULL;
8737     int numPGNTags = 0;
8738     int err;
8739     GameMode oldGameMode;
8740     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8741
8742     if (appData.debugMode) 
8743         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8744
8745     if (gameMode == Training )
8746         SetTrainingModeOff();
8747
8748     oldGameMode = gameMode;
8749     if (gameMode != BeginningOfGame) {
8750       Reset(FALSE, TRUE);
8751     }
8752
8753     gameFileFP = f;
8754     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8755         fclose(lastLoadGameFP);
8756     }
8757
8758     if (useList) {
8759         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8760         
8761         if (lg) {
8762             fseek(f, lg->offset, 0);
8763             GameListHighlight(gameNumber);
8764             gn = 1;
8765         }
8766         else {
8767             DisplayError(_("Game number out of range"), 0);
8768             return FALSE;
8769         }
8770     } else {
8771         GameListDestroy();
8772         if (fseek(f, 0, 0) == -1) {
8773             if (f == lastLoadGameFP ?
8774                 gameNumber == lastLoadGameNumber + 1 :
8775                 gameNumber == 1) {
8776                 gn = 1;
8777             } else {
8778                 DisplayError(_("Can't seek on game file"), 0);
8779                 return FALSE;
8780             }
8781         }
8782     }
8783     lastLoadGameFP = f;
8784     lastLoadGameNumber = gameNumber;
8785     strcpy(lastLoadGameTitle, title);
8786     lastLoadGameUseList = useList;
8787
8788     yynewfile(f);
8789
8790     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8791       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8792                 lg->gameInfo.black);
8793             DisplayTitle(buf);
8794     } else if (*title != NULLCHAR) {
8795         if (gameNumber > 1) {
8796             sprintf(buf, "%s %d", title, gameNumber);
8797             DisplayTitle(buf);
8798         } else {
8799             DisplayTitle(title);
8800         }
8801     }
8802
8803     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8804         gameMode = PlayFromGameFile;
8805         ModeHighlight();
8806     }
8807
8808     currentMove = forwardMostMove = backwardMostMove = 0;
8809     CopyBoard(boards[0], initialPosition);
8810     StopClocks();
8811
8812     /*
8813      * Skip the first gn-1 games in the file.
8814      * Also skip over anything that precedes an identifiable 
8815      * start of game marker, to avoid being confused by 
8816      * garbage at the start of the file.  Currently 
8817      * recognized start of game markers are the move number "1",
8818      * the pattern "gnuchess .* game", the pattern
8819      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8820      * A game that starts with one of the latter two patterns
8821      * will also have a move number 1, possibly
8822      * following a position diagram.
8823      * 5-4-02: Let's try being more lenient and allowing a game to
8824      * start with an unnumbered move.  Does that break anything?
8825      */
8826     cm = lastLoadGameStart = (ChessMove) 0;
8827     while (gn > 0) {
8828         yyboardindex = forwardMostMove;
8829         cm = (ChessMove) yylex();
8830         switch (cm) {
8831           case (ChessMove) 0:
8832             if (cmailMsgLoaded) {
8833                 nCmailGames = CMAIL_MAX_GAMES - gn;
8834             } else {
8835                 Reset(TRUE, TRUE);
8836                 DisplayError(_("Game not found in file"), 0);
8837             }
8838             return FALSE;
8839
8840           case GNUChessGame:
8841           case XBoardGame:
8842             gn--;
8843             lastLoadGameStart = cm;
8844             break;
8845             
8846           case MoveNumberOne:
8847             switch (lastLoadGameStart) {
8848               case GNUChessGame:
8849               case XBoardGame:
8850               case PGNTag:
8851                 break;
8852               case MoveNumberOne:
8853               case (ChessMove) 0:
8854                 gn--;           /* count this game */
8855                 lastLoadGameStart = cm;
8856                 break;
8857               default:
8858                 /* impossible */
8859                 break;
8860             }
8861             break;
8862
8863           case PGNTag:
8864             switch (lastLoadGameStart) {
8865               case GNUChessGame:
8866               case PGNTag:
8867               case MoveNumberOne:
8868               case (ChessMove) 0:
8869                 gn--;           /* count this game */
8870                 lastLoadGameStart = cm;
8871                 break;
8872               case XBoardGame:
8873                 lastLoadGameStart = cm; /* game counted already */
8874                 break;
8875               default:
8876                 /* impossible */
8877                 break;
8878             }
8879             if (gn > 0) {
8880                 do {
8881                     yyboardindex = forwardMostMove;
8882                     cm = (ChessMove) yylex();
8883                 } while (cm == PGNTag || cm == Comment);
8884             }
8885             break;
8886
8887           case WhiteWins:
8888           case BlackWins:
8889           case GameIsDrawn:
8890             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8891                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8892                     != CMAIL_OLD_RESULT) {
8893                     nCmailResults ++ ;
8894                     cmailResult[  CMAIL_MAX_GAMES
8895                                 - gn - 1] = CMAIL_OLD_RESULT;
8896                 }
8897             }
8898             break;
8899
8900           case NormalMove:
8901             /* Only a NormalMove can be at the start of a game
8902              * without a position diagram. */
8903             if (lastLoadGameStart == (ChessMove) 0) {
8904               gn--;
8905               lastLoadGameStart = MoveNumberOne;
8906             }
8907             break;
8908
8909           default:
8910             break;
8911         }
8912     }
8913     
8914     if (appData.debugMode)
8915       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8916
8917     if (cm == XBoardGame) {
8918         /* Skip any header junk before position diagram and/or move 1 */
8919         for (;;) {
8920             yyboardindex = forwardMostMove;
8921             cm = (ChessMove) yylex();
8922
8923             if (cm == (ChessMove) 0 ||
8924                 cm == GNUChessGame || cm == XBoardGame) {
8925                 /* Empty game; pretend end-of-file and handle later */
8926                 cm = (ChessMove) 0;
8927                 break;
8928             }
8929
8930             if (cm == MoveNumberOne || cm == PositionDiagram ||
8931                 cm == PGNTag || cm == Comment)
8932               break;
8933         }
8934     } else if (cm == GNUChessGame) {
8935         if (gameInfo.event != NULL) {
8936             free(gameInfo.event);
8937         }
8938         gameInfo.event = StrSave(yy_text);
8939     }   
8940
8941     startedFromSetupPosition = FALSE;
8942     while (cm == PGNTag) {
8943         if (appData.debugMode) 
8944           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8945         err = ParsePGNTag(yy_text, &gameInfo);
8946         if (!err) numPGNTags++;
8947
8948         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8949         if(gameInfo.variant != oldVariant) {
8950             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8951             InitPosition(TRUE);
8952             oldVariant = gameInfo.variant;
8953             if (appData.debugMode) 
8954               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8955         }
8956
8957
8958         if (gameInfo.fen != NULL) {
8959           Board initial_position;
8960           startedFromSetupPosition = TRUE;
8961           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8962             Reset(TRUE, TRUE);
8963             DisplayError(_("Bad FEN position in file"), 0);
8964             return FALSE;
8965           }
8966           CopyBoard(boards[0], initial_position);
8967           if (blackPlaysFirst) {
8968             currentMove = forwardMostMove = backwardMostMove = 1;
8969             CopyBoard(boards[1], initial_position);
8970             strcpy(moveList[0], "");
8971             strcpy(parseList[0], "");
8972             timeRemaining[0][1] = whiteTimeRemaining;
8973             timeRemaining[1][1] = blackTimeRemaining;
8974             if (commentList[0] != NULL) {
8975               commentList[1] = commentList[0];
8976               commentList[0] = NULL;
8977             }
8978           } else {
8979             currentMove = forwardMostMove = backwardMostMove = 0;
8980           }
8981           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8982           {   int i;
8983               initialRulePlies = FENrulePlies;
8984               epStatus[forwardMostMove] = FENepStatus;
8985               for( i=0; i< nrCastlingRights; i++ )
8986                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8987           }
8988           yyboardindex = forwardMostMove;
8989           free(gameInfo.fen);
8990           gameInfo.fen = NULL;
8991         }
8992
8993         yyboardindex = forwardMostMove;
8994         cm = (ChessMove) yylex();
8995
8996         /* Handle comments interspersed among the tags */
8997         while (cm == Comment) {
8998             char *p;
8999             if (appData.debugMode) 
9000               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9001             p = yy_text;
9002             if (*p == '{' || *p == '[' || *p == '(') {
9003                 p[strlen(p) - 1] = NULLCHAR;
9004                 p++;
9005             }
9006             while (*p == '\n') p++;
9007             AppendComment(currentMove, p);
9008             yyboardindex = forwardMostMove;
9009             cm = (ChessMove) yylex();
9010         }
9011     }
9012
9013     /* don't rely on existence of Event tag since if game was
9014      * pasted from clipboard the Event tag may not exist
9015      */
9016     if (numPGNTags > 0){
9017         char *tags;
9018         if (gameInfo.variant == VariantNormal) {
9019           gameInfo.variant = StringToVariant(gameInfo.event);
9020         }
9021         if (!matchMode) {
9022           if( appData.autoDisplayTags ) {
9023             tags = PGNTags(&gameInfo);
9024             TagsPopUp(tags, CmailMsg());
9025             free(tags);
9026           }
9027         }
9028     } else {
9029         /* Make something up, but don't display it now */
9030         SetGameInfo();
9031         TagsPopDown();
9032     }
9033
9034     if (cm == PositionDiagram) {
9035         int i, j;
9036         char *p;
9037         Board initial_position;
9038
9039         if (appData.debugMode)
9040           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9041
9042         if (!startedFromSetupPosition) {
9043             p = yy_text;
9044             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9045               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9046                 switch (*p) {
9047                   case '[':
9048                   case '-':
9049                   case ' ':
9050                   case '\t':
9051                   case '\n':
9052                   case '\r':
9053                     break;
9054                   default:
9055                     initial_position[i][j++] = CharToPiece(*p);
9056                     break;
9057                 }
9058             while (*p == ' ' || *p == '\t' ||
9059                    *p == '\n' || *p == '\r') p++;
9060         
9061             if (strncmp(p, "black", strlen("black"))==0)
9062               blackPlaysFirst = TRUE;
9063             else
9064               blackPlaysFirst = FALSE;
9065             startedFromSetupPosition = TRUE;
9066         
9067             CopyBoard(boards[0], initial_position);
9068             if (blackPlaysFirst) {
9069                 currentMove = forwardMostMove = backwardMostMove = 1;
9070                 CopyBoard(boards[1], initial_position);
9071                 strcpy(moveList[0], "");
9072                 strcpy(parseList[0], "");
9073                 timeRemaining[0][1] = whiteTimeRemaining;
9074                 timeRemaining[1][1] = blackTimeRemaining;
9075                 if (commentList[0] != NULL) {
9076                     commentList[1] = commentList[0];
9077                     commentList[0] = NULL;
9078                 }
9079             } else {
9080                 currentMove = forwardMostMove = backwardMostMove = 0;
9081             }
9082         }
9083         yyboardindex = forwardMostMove;
9084         cm = (ChessMove) yylex();
9085     }
9086
9087     if (first.pr == NoProc) {
9088         StartChessProgram(&first);
9089     }
9090     InitChessProgram(&first, FALSE);
9091     SendToProgram("force\n", &first);
9092     if (startedFromSetupPosition) {
9093         SendBoard(&first, forwardMostMove);
9094     if (appData.debugMode) {
9095         fprintf(debugFP, "Load Game\n");
9096     }
9097         DisplayBothClocks();
9098     }      
9099
9100     /* [HGM] server: flag to write setup moves in broadcast file as one */
9101     loadFlag = appData.suppressLoadMoves;
9102
9103     while (cm == Comment) {
9104         char *p;
9105         if (appData.debugMode) 
9106           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9107         p = yy_text;
9108         if (*p == '{' || *p == '[' || *p == '(') {
9109             p[strlen(p) - 1] = NULLCHAR;
9110             p++;
9111         }
9112         while (*p == '\n') p++;
9113         AppendComment(currentMove, p);
9114         yyboardindex = forwardMostMove;
9115         cm = (ChessMove) yylex();
9116     }
9117
9118     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9119         cm == WhiteWins || cm == BlackWins ||
9120         cm == GameIsDrawn || cm == GameUnfinished) {
9121         DisplayMessage("", _("No moves in game"));
9122         if (cmailMsgLoaded) {
9123             if (appData.debugMode)
9124               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9125             ClearHighlights();
9126             flipView = FALSE;
9127         }
9128         DrawPosition(FALSE, boards[currentMove]);
9129         DisplayBothClocks();
9130         gameMode = EditGame;
9131         ModeHighlight();
9132         gameFileFP = NULL;
9133         cmailOldMove = 0;
9134         return TRUE;
9135     }
9136
9137     // [HGM] PV info: routine tests if comment empty
9138     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9139         DisplayComment(currentMove - 1, commentList[currentMove]);
9140     }
9141     if (!matchMode && appData.timeDelay != 0) 
9142       DrawPosition(FALSE, boards[currentMove]);
9143
9144     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9145       programStats.ok_to_send = 1;
9146     }
9147
9148     /* if the first token after the PGN tags is a move
9149      * and not move number 1, retrieve it from the parser 
9150      */
9151     if (cm != MoveNumberOne)
9152         LoadGameOneMove(cm);
9153
9154     /* load the remaining moves from the file */
9155     while (LoadGameOneMove((ChessMove)0)) {
9156       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9157       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9158     }
9159
9160     /* rewind to the start of the game */
9161     currentMove = backwardMostMove;
9162
9163     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9164
9165     if (oldGameMode == AnalyzeFile ||
9166         oldGameMode == AnalyzeMode) {
9167       AnalyzeFileEvent();
9168     }
9169
9170     if (matchMode || appData.timeDelay == 0) {
9171       ToEndEvent();
9172       gameMode = EditGame;
9173       ModeHighlight();
9174     } else if (appData.timeDelay > 0) {
9175       AutoPlayGameLoop();
9176     }
9177
9178     if (appData.debugMode) 
9179         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9180
9181     loadFlag = 0; /* [HGM] true game starts */
9182     return TRUE;
9183 }
9184
9185 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9186 int
9187 ReloadPosition(offset)
9188      int offset;
9189 {
9190     int positionNumber = lastLoadPositionNumber + offset;
9191     if (lastLoadPositionFP == NULL) {
9192         DisplayError(_("No position has been loaded yet"), 0);
9193         return FALSE;
9194     }
9195     if (positionNumber <= 0) {
9196         DisplayError(_("Can't back up any further"), 0);
9197         return FALSE;
9198     }
9199     return LoadPosition(lastLoadPositionFP, positionNumber,
9200                         lastLoadPositionTitle);
9201 }
9202
9203 /* Load the nth position from the given file */
9204 int
9205 LoadPositionFromFile(filename, n, title)
9206      char *filename;
9207      int n;
9208      char *title;
9209 {
9210     FILE *f;
9211     char buf[MSG_SIZ];
9212
9213     if (strcmp(filename, "-") == 0) {
9214         return LoadPosition(stdin, n, "stdin");
9215     } else {
9216         f = fopen(filename, "rb");
9217         if (f == NULL) {
9218             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9219             DisplayError(buf, errno);
9220             return FALSE;
9221         } else {
9222             return LoadPosition(f, n, title);
9223         }
9224     }
9225 }
9226
9227 /* Load the nth position from the given open file, and close it */
9228 int
9229 LoadPosition(f, positionNumber, title)
9230      FILE *f;
9231      int positionNumber;
9232      char *title;
9233 {
9234     char *p, line[MSG_SIZ];
9235     Board initial_position;
9236     int i, j, fenMode, pn;
9237     
9238     if (gameMode == Training )
9239         SetTrainingModeOff();
9240
9241     if (gameMode != BeginningOfGame) {
9242         Reset(FALSE, TRUE);
9243     }
9244     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9245         fclose(lastLoadPositionFP);
9246     }
9247     if (positionNumber == 0) positionNumber = 1;
9248     lastLoadPositionFP = f;
9249     lastLoadPositionNumber = positionNumber;
9250     strcpy(lastLoadPositionTitle, title);
9251     if (first.pr == NoProc) {
9252       StartChessProgram(&first);
9253       InitChessProgram(&first, FALSE);
9254     }    
9255     pn = positionNumber;
9256     if (positionNumber < 0) {
9257         /* Negative position number means to seek to that byte offset */
9258         if (fseek(f, -positionNumber, 0) == -1) {
9259             DisplayError(_("Can't seek on position file"), 0);
9260             return FALSE;
9261         };
9262         pn = 1;
9263     } else {
9264         if (fseek(f, 0, 0) == -1) {
9265             if (f == lastLoadPositionFP ?
9266                 positionNumber == lastLoadPositionNumber + 1 :
9267                 positionNumber == 1) {
9268                 pn = 1;
9269             } else {
9270                 DisplayError(_("Can't seek on position file"), 0);
9271                 return FALSE;
9272             }
9273         }
9274     }
9275     /* See if this file is FEN or old-style xboard */
9276     if (fgets(line, MSG_SIZ, f) == NULL) {
9277         DisplayError(_("Position not found in file"), 0);
9278         return FALSE;
9279     }
9280     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9281     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9282
9283     if (pn >= 2) {
9284         if (fenMode || line[0] == '#') pn--;
9285         while (pn > 0) {
9286             /* skip positions before number pn */
9287             if (fgets(line, MSG_SIZ, f) == NULL) {
9288                 Reset(TRUE, TRUE);
9289                 DisplayError(_("Position not found in file"), 0);
9290                 return FALSE;
9291             }
9292             if (fenMode || line[0] == '#') pn--;
9293         }
9294     }
9295
9296     if (fenMode) {
9297         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9298             DisplayError(_("Bad FEN position in file"), 0);
9299             return FALSE;
9300         }
9301     } else {
9302         (void) fgets(line, MSG_SIZ, f);
9303         (void) fgets(line, MSG_SIZ, f);
9304     
9305         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9306             (void) fgets(line, MSG_SIZ, f);
9307             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9308                 if (*p == ' ')
9309                   continue;
9310                 initial_position[i][j++] = CharToPiece(*p);
9311             }
9312         }
9313     
9314         blackPlaysFirst = FALSE;
9315         if (!feof(f)) {
9316             (void) fgets(line, MSG_SIZ, f);
9317             if (strncmp(line, "black", strlen("black"))==0)
9318               blackPlaysFirst = TRUE;
9319         }
9320     }
9321     startedFromSetupPosition = TRUE;
9322     
9323     SendToProgram("force\n", &first);
9324     CopyBoard(boards[0], initial_position);
9325     if (blackPlaysFirst) {
9326         currentMove = forwardMostMove = backwardMostMove = 1;
9327         strcpy(moveList[0], "");
9328         strcpy(parseList[0], "");
9329         CopyBoard(boards[1], initial_position);
9330         DisplayMessage("", _("Black to play"));
9331     } else {
9332         currentMove = forwardMostMove = backwardMostMove = 0;
9333         DisplayMessage("", _("White to play"));
9334     }
9335           /* [HGM] copy FEN attributes as well */
9336           {   int i;
9337               initialRulePlies = FENrulePlies;
9338               epStatus[forwardMostMove] = FENepStatus;
9339               for( i=0; i< nrCastlingRights; i++ )
9340                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9341           }
9342     SendBoard(&first, forwardMostMove);
9343     if (appData.debugMode) {
9344 int i, j;
9345   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9346   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9347         fprintf(debugFP, "Load Position\n");
9348     }
9349
9350     if (positionNumber > 1) {
9351         sprintf(line, "%s %d", title, positionNumber);
9352         DisplayTitle(line);
9353     } else {
9354         DisplayTitle(title);
9355     }
9356     gameMode = EditGame;
9357     ModeHighlight();
9358     ResetClocks();
9359     timeRemaining[0][1] = whiteTimeRemaining;
9360     timeRemaining[1][1] = blackTimeRemaining;
9361     DrawPosition(FALSE, boards[currentMove]);
9362    
9363     return TRUE;
9364 }
9365
9366
9367 void
9368 CopyPlayerNameIntoFileName(dest, src)
9369      char **dest, *src;
9370 {
9371     while (*src != NULLCHAR && *src != ',') {
9372         if (*src == ' ') {
9373             *(*dest)++ = '_';
9374             src++;
9375         } else {
9376             *(*dest)++ = *src++;
9377         }
9378     }
9379 }
9380
9381 char *DefaultFileName(ext)
9382      char *ext;
9383 {
9384     static char def[MSG_SIZ];
9385     char *p;
9386
9387     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9388         p = def;
9389         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9390         *p++ = '-';
9391         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9392         *p++ = '.';
9393         strcpy(p, ext);
9394     } else {
9395         def[0] = NULLCHAR;
9396     }
9397     return def;
9398 }
9399
9400 /* Save the current game to the given file */
9401 int
9402 SaveGameToFile(filename, append)
9403      char *filename;
9404      int append;
9405 {
9406     FILE *f;
9407     char buf[MSG_SIZ];
9408
9409     if (strcmp(filename, "-") == 0) {
9410         return SaveGame(stdout, 0, NULL);
9411     } else {
9412         f = fopen(filename, append ? "a" : "w");
9413         if (f == NULL) {
9414             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9415             DisplayError(buf, errno);
9416             return FALSE;
9417         } else {
9418             return SaveGame(f, 0, NULL);
9419         }
9420     }
9421 }
9422
9423 char *
9424 SavePart(str)
9425      char *str;
9426 {
9427     static char buf[MSG_SIZ];
9428     char *p;
9429     
9430     p = strchr(str, ' ');
9431     if (p == NULL) return str;
9432     strncpy(buf, str, p - str);
9433     buf[p - str] = NULLCHAR;
9434     return buf;
9435 }
9436
9437 #define PGN_MAX_LINE 75
9438
9439 #define PGN_SIDE_WHITE  0
9440 #define PGN_SIDE_BLACK  1
9441
9442 /* [AS] */
9443 static int FindFirstMoveOutOfBook( int side )
9444 {
9445     int result = -1;
9446
9447     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9448         int index = backwardMostMove;
9449         int has_book_hit = 0;
9450
9451         if( (index % 2) != side ) {
9452             index++;
9453         }
9454
9455         while( index < forwardMostMove ) {
9456             /* Check to see if engine is in book */
9457             int depth = pvInfoList[index].depth;
9458             int score = pvInfoList[index].score;
9459             int in_book = 0;
9460
9461             if( depth <= 2 ) {
9462                 in_book = 1;
9463             }
9464             else if( score == 0 && depth == 63 ) {
9465                 in_book = 1; /* Zappa */
9466             }
9467             else if( score == 2 && depth == 99 ) {
9468                 in_book = 1; /* Abrok */
9469             }
9470
9471             has_book_hit += in_book;
9472
9473             if( ! in_book ) {
9474                 result = index;
9475
9476                 break;
9477             }
9478
9479             index += 2;
9480         }
9481     }
9482
9483     return result;
9484 }
9485
9486 /* [AS] */
9487 void GetOutOfBookInfo( char * buf )
9488 {
9489     int oob[2];
9490     int i;
9491     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9492
9493     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9494     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9495
9496     *buf = '\0';
9497
9498     if( oob[0] >= 0 || oob[1] >= 0 ) {
9499         for( i=0; i<2; i++ ) {
9500             int idx = oob[i];
9501
9502             if( idx >= 0 ) {
9503                 if( i > 0 && oob[0] >= 0 ) {
9504                     strcat( buf, "   " );
9505                 }
9506
9507                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9508                 sprintf( buf+strlen(buf), "%s%.2f", 
9509                     pvInfoList[idx].score >= 0 ? "+" : "",
9510                     pvInfoList[idx].score / 100.0 );
9511             }
9512         }
9513     }
9514 }
9515
9516 /* Save game in PGN style and close the file */
9517 int
9518 SaveGamePGN(f)
9519      FILE *f;
9520 {
9521     int i, offset, linelen, newblock;
9522     time_t tm;
9523 //    char *movetext;
9524     char numtext[32];
9525     int movelen, numlen, blank;
9526     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9527
9528     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9529     
9530     tm = time((time_t *) NULL);
9531     
9532     PrintPGNTags(f, &gameInfo);
9533     
9534     if (backwardMostMove > 0 || startedFromSetupPosition) {
9535         char *fen = PositionToFEN(backwardMostMove, NULL);
9536         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9537         fprintf(f, "\n{--------------\n");
9538         PrintPosition(f, backwardMostMove);
9539         fprintf(f, "--------------}\n");
9540         free(fen);
9541     }
9542     else {
9543         /* [AS] Out of book annotation */
9544         if( appData.saveOutOfBookInfo ) {
9545             char buf[64];
9546
9547             GetOutOfBookInfo( buf );
9548
9549             if( buf[0] != '\0' ) {
9550                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9551             }
9552         }
9553
9554         fprintf(f, "\n");
9555     }
9556
9557     i = backwardMostMove;
9558     linelen = 0;
9559     newblock = TRUE;
9560
9561     while (i < forwardMostMove) {
9562         /* Print comments preceding this move */
9563         if (commentList[i] != NULL) {
9564             if (linelen > 0) fprintf(f, "\n");
9565             fprintf(f, "{\n%s}\n", commentList[i]);
9566             linelen = 0;
9567             newblock = TRUE;
9568         }
9569
9570         /* Format move number */
9571         if ((i % 2) == 0) {
9572             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9573         } else {
9574             if (newblock) {
9575                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9576             } else {
9577                 numtext[0] = NULLCHAR;
9578             }
9579         }
9580         numlen = strlen(numtext);
9581         newblock = FALSE;
9582
9583         /* Print move number */
9584         blank = linelen > 0 && numlen > 0;
9585         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9586             fprintf(f, "\n");
9587             linelen = 0;
9588             blank = 0;
9589         }
9590         if (blank) {
9591             fprintf(f, " ");
9592             linelen++;
9593         }
9594         fprintf(f, numtext);
9595         linelen += numlen;
9596
9597         /* Get move */
9598         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9599         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9600
9601         /* Print move */
9602         blank = linelen > 0 && movelen > 0;
9603         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9604             fprintf(f, "\n");
9605             linelen = 0;
9606             blank = 0;
9607         }
9608         if (blank) {
9609             fprintf(f, " ");
9610             linelen++;
9611         }
9612         fprintf(f, move_buffer);
9613         linelen += movelen;
9614
9615         /* [AS] Add PV info if present */
9616         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9617             /* [HGM] add time */
9618             char buf[MSG_SIZ]; int seconds = 0;
9619
9620             if(i >= backwardMostMove) {
9621                 if(WhiteOnMove(i))
9622                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9623                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9624                 else
9625                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9626                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9627             }
9628             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9629
9630             if( seconds <= 0) buf[0] = 0; else
9631             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9632                 seconds = (seconds + 4)/10; // round to full seconds
9633                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9634                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9635             }
9636
9637             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9638                 pvInfoList[i].score >= 0 ? "+" : "",
9639                 pvInfoList[i].score / 100.0,
9640                 pvInfoList[i].depth,
9641                 buf );
9642
9643             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9644
9645             /* Print score/depth */
9646             blank = linelen > 0 && movelen > 0;
9647             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9648                 fprintf(f, "\n");
9649                 linelen = 0;
9650                 blank = 0;
9651             }
9652             if (blank) {
9653                 fprintf(f, " ");
9654                 linelen++;
9655             }
9656             fprintf(f, move_buffer);
9657             linelen += movelen;
9658         }
9659
9660         i++;
9661     }
9662     
9663     /* Start a new line */
9664     if (linelen > 0) fprintf(f, "\n");
9665
9666     /* Print comments after last move */
9667     if (commentList[i] != NULL) {
9668         fprintf(f, "{\n%s}\n", commentList[i]);
9669     }
9670
9671     /* Print result */
9672     if (gameInfo.resultDetails != NULL &&
9673         gameInfo.resultDetails[0] != NULLCHAR) {
9674         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9675                 PGNResult(gameInfo.result));
9676     } else {
9677         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9678     }
9679
9680     fclose(f);
9681     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9682     return TRUE;
9683 }
9684
9685 /* Save game in old style and close the file */
9686 int
9687 SaveGameOldStyle(f)
9688      FILE *f;
9689 {
9690     int i, offset;
9691     time_t tm;
9692     
9693     tm = time((time_t *) NULL);
9694     
9695     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9696     PrintOpponents(f);
9697     
9698     if (backwardMostMove > 0 || startedFromSetupPosition) {
9699         fprintf(f, "\n[--------------\n");
9700         PrintPosition(f, backwardMostMove);
9701         fprintf(f, "--------------]\n");
9702     } else {
9703         fprintf(f, "\n");
9704     }
9705
9706     i = backwardMostMove;
9707     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9708
9709     while (i < forwardMostMove) {
9710         if (commentList[i] != NULL) {
9711             fprintf(f, "[%s]\n", commentList[i]);
9712         }
9713
9714         if ((i % 2) == 1) {
9715             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9716             i++;
9717         } else {
9718             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9719             i++;
9720             if (commentList[i] != NULL) {
9721                 fprintf(f, "\n");
9722                 continue;
9723             }
9724             if (i >= forwardMostMove) {
9725                 fprintf(f, "\n");
9726                 break;
9727             }
9728             fprintf(f, "%s\n", parseList[i]);
9729             i++;
9730         }
9731     }
9732     
9733     if (commentList[i] != NULL) {
9734         fprintf(f, "[%s]\n", commentList[i]);
9735     }
9736
9737     /* This isn't really the old style, but it's close enough */
9738     if (gameInfo.resultDetails != NULL &&
9739         gameInfo.resultDetails[0] != NULLCHAR) {
9740         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9741                 gameInfo.resultDetails);
9742     } else {
9743         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9744     }
9745
9746     fclose(f);
9747     return TRUE;
9748 }
9749
9750 /* Save the current game to open file f and close the file */
9751 int
9752 SaveGame(f, dummy, dummy2)
9753      FILE *f;
9754      int dummy;
9755      char *dummy2;
9756 {
9757     if (gameMode == EditPosition) EditPositionDone();
9758     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9759     if (appData.oldSaveStyle)
9760       return SaveGameOldStyle(f);
9761     else
9762       return SaveGamePGN(f);
9763 }
9764
9765 /* Save the current position to the given file */
9766 int
9767 SavePositionToFile(filename)
9768      char *filename;
9769 {
9770     FILE *f;
9771     char buf[MSG_SIZ];
9772
9773     if (strcmp(filename, "-") == 0) {
9774         return SavePosition(stdout, 0, NULL);
9775     } else {
9776         f = fopen(filename, "a");
9777         if (f == NULL) {
9778             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9779             DisplayError(buf, errno);
9780             return FALSE;
9781         } else {
9782             SavePosition(f, 0, NULL);
9783             return TRUE;
9784         }
9785     }
9786 }
9787
9788 /* Save the current position to the given open file and close the file */
9789 int
9790 SavePosition(f, dummy, dummy2)
9791      FILE *f;
9792      int dummy;
9793      char *dummy2;
9794 {
9795     time_t tm;
9796     char *fen;
9797     
9798     if (appData.oldSaveStyle) {
9799         tm = time((time_t *) NULL);
9800     
9801         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9802         PrintOpponents(f);
9803         fprintf(f, "[--------------\n");
9804         PrintPosition(f, currentMove);
9805         fprintf(f, "--------------]\n");
9806     } else {
9807         fen = PositionToFEN(currentMove, NULL);
9808         fprintf(f, "%s\n", fen);
9809         free(fen);
9810     }
9811     fclose(f);
9812     return TRUE;
9813 }
9814
9815 void
9816 ReloadCmailMsgEvent(unregister)
9817      int unregister;
9818 {
9819 #if !WIN32
9820     static char *inFilename = NULL;
9821     static char *outFilename;
9822     int i;
9823     struct stat inbuf, outbuf;
9824     int status;
9825     
9826     /* Any registered moves are unregistered if unregister is set, */
9827     /* i.e. invoked by the signal handler */
9828     if (unregister) {
9829         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9830             cmailMoveRegistered[i] = FALSE;
9831             if (cmailCommentList[i] != NULL) {
9832                 free(cmailCommentList[i]);
9833                 cmailCommentList[i] = NULL;
9834             }
9835         }
9836         nCmailMovesRegistered = 0;
9837     }
9838
9839     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9840         cmailResult[i] = CMAIL_NOT_RESULT;
9841     }
9842     nCmailResults = 0;
9843
9844     if (inFilename == NULL) {
9845         /* Because the filenames are static they only get malloced once  */
9846         /* and they never get freed                                      */
9847         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9848         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9849
9850         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9851         sprintf(outFilename, "%s.out", appData.cmailGameName);
9852     }
9853     
9854     status = stat(outFilename, &outbuf);
9855     if (status < 0) {
9856         cmailMailedMove = FALSE;
9857     } else {
9858         status = stat(inFilename, &inbuf);
9859         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9860     }
9861     
9862     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9863        counts the games, notes how each one terminated, etc.
9864        
9865        It would be nice to remove this kludge and instead gather all
9866        the information while building the game list.  (And to keep it
9867        in the game list nodes instead of having a bunch of fixed-size
9868        parallel arrays.)  Note this will require getting each game's
9869        termination from the PGN tags, as the game list builder does
9870        not process the game moves.  --mann
9871        */
9872     cmailMsgLoaded = TRUE;
9873     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9874     
9875     /* Load first game in the file or popup game menu */
9876     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9877
9878 #endif /* !WIN32 */
9879     return;
9880 }
9881
9882 int
9883 RegisterMove()
9884 {
9885     FILE *f;
9886     char string[MSG_SIZ];
9887
9888     if (   cmailMailedMove
9889         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9890         return TRUE;            /* Allow free viewing  */
9891     }
9892
9893     /* Unregister move to ensure that we don't leave RegisterMove        */
9894     /* with the move registered when the conditions for registering no   */
9895     /* longer hold                                                       */
9896     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9897         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9898         nCmailMovesRegistered --;
9899
9900         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9901           {
9902               free(cmailCommentList[lastLoadGameNumber - 1]);
9903               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9904           }
9905     }
9906
9907     if (cmailOldMove == -1) {
9908         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9909         return FALSE;
9910     }
9911
9912     if (currentMove > cmailOldMove + 1) {
9913         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9914         return FALSE;
9915     }
9916
9917     if (currentMove < cmailOldMove) {
9918         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9919         return FALSE;
9920     }
9921
9922     if (forwardMostMove > currentMove) {
9923         /* Silently truncate extra moves */
9924         TruncateGame();
9925     }
9926
9927     if (   (currentMove == cmailOldMove + 1)
9928         || (   (currentMove == cmailOldMove)
9929             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9930                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9931         if (gameInfo.result != GameUnfinished) {
9932             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9933         }
9934
9935         if (commentList[currentMove] != NULL) {
9936             cmailCommentList[lastLoadGameNumber - 1]
9937               = StrSave(commentList[currentMove]);
9938         }
9939         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9940
9941         if (appData.debugMode)
9942           fprintf(debugFP, "Saving %s for game %d\n",
9943                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9944
9945         sprintf(string,
9946                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9947         
9948         f = fopen(string, "w");
9949         if (appData.oldSaveStyle) {
9950             SaveGameOldStyle(f); /* also closes the file */
9951             
9952             sprintf(string, "%s.pos.out", appData.cmailGameName);
9953             f = fopen(string, "w");
9954             SavePosition(f, 0, NULL); /* also closes the file */
9955         } else {
9956             fprintf(f, "{--------------\n");
9957             PrintPosition(f, currentMove);
9958             fprintf(f, "--------------}\n\n");
9959             
9960             SaveGame(f, 0, NULL); /* also closes the file*/
9961         }
9962         
9963         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9964         nCmailMovesRegistered ++;
9965     } else if (nCmailGames == 1) {
9966         DisplayError(_("You have not made a move yet"), 0);
9967         return FALSE;
9968     }
9969
9970     return TRUE;
9971 }
9972
9973 void
9974 MailMoveEvent()
9975 {
9976 #if !WIN32
9977     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9978     FILE *commandOutput;
9979     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9980     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9981     int nBuffers;
9982     int i;
9983     int archived;
9984     char *arcDir;
9985
9986     if (! cmailMsgLoaded) {
9987         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9988         return;
9989     }
9990
9991     if (nCmailGames == nCmailResults) {
9992         DisplayError(_("No unfinished games"), 0);
9993         return;
9994     }
9995
9996 #if CMAIL_PROHIBIT_REMAIL
9997     if (cmailMailedMove) {
9998         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);
9999         DisplayError(msg, 0);
10000         return;
10001     }
10002 #endif
10003
10004     if (! (cmailMailedMove || RegisterMove())) return;
10005     
10006     if (   cmailMailedMove
10007         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10008         sprintf(string, partCommandString,
10009                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10010         commandOutput = popen(string, "r");
10011
10012         if (commandOutput == NULL) {
10013             DisplayError(_("Failed to invoke cmail"), 0);
10014         } else {
10015             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10016                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10017             }
10018             if (nBuffers > 1) {
10019                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10020                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10021                 nBytes = MSG_SIZ - 1;
10022             } else {
10023                 (void) memcpy(msg, buffer, nBytes);
10024             }
10025             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10026
10027             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10028                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10029
10030                 archived = TRUE;
10031                 for (i = 0; i < nCmailGames; i ++) {
10032                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10033                         archived = FALSE;
10034                     }
10035                 }
10036                 if (   archived
10037                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10038                         != NULL)) {
10039                     sprintf(buffer, "%s/%s.%s.archive",
10040                             arcDir,
10041                             appData.cmailGameName,
10042                             gameInfo.date);
10043                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10044                     cmailMsgLoaded = FALSE;
10045                 }
10046             }
10047
10048             DisplayInformation(msg);
10049             pclose(commandOutput);
10050         }
10051     } else {
10052         if ((*cmailMsg) != '\0') {
10053             DisplayInformation(cmailMsg);
10054         }
10055     }
10056
10057     return;
10058 #endif /* !WIN32 */
10059 }
10060
10061 char *
10062 CmailMsg()
10063 {
10064 #if WIN32
10065     return NULL;
10066 #else
10067     int  prependComma = 0;
10068     char number[5];
10069     char string[MSG_SIZ];       /* Space for game-list */
10070     int  i;
10071     
10072     if (!cmailMsgLoaded) return "";
10073
10074     if (cmailMailedMove) {
10075         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10076     } else {
10077         /* Create a list of games left */
10078         sprintf(string, "[");
10079         for (i = 0; i < nCmailGames; i ++) {
10080             if (! (   cmailMoveRegistered[i]
10081                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10082                 if (prependComma) {
10083                     sprintf(number, ",%d", i + 1);
10084                 } else {
10085                     sprintf(number, "%d", i + 1);
10086                     prependComma = 1;
10087                 }
10088                 
10089                 strcat(string, number);
10090             }
10091         }
10092         strcat(string, "]");
10093
10094         if (nCmailMovesRegistered + nCmailResults == 0) {
10095             switch (nCmailGames) {
10096               case 1:
10097                 sprintf(cmailMsg,
10098                         _("Still need to make move for game\n"));
10099                 break;
10100                 
10101               case 2:
10102                 sprintf(cmailMsg,
10103                         _("Still need to make moves for both games\n"));
10104                 break;
10105                 
10106               default:
10107                 sprintf(cmailMsg,
10108                         _("Still need to make moves for all %d games\n"),
10109                         nCmailGames);
10110                 break;
10111             }
10112         } else {
10113             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10114               case 1:
10115                 sprintf(cmailMsg,
10116                         _("Still need to make a move for game %s\n"),
10117                         string);
10118                 break;
10119                 
10120               case 0:
10121                 if (nCmailResults == nCmailGames) {
10122                     sprintf(cmailMsg, _("No unfinished games\n"));
10123                 } else {
10124                     sprintf(cmailMsg, _("Ready to send mail\n"));
10125                 }
10126                 break;
10127                 
10128               default:
10129                 sprintf(cmailMsg,
10130                         _("Still need to make moves for games %s\n"),
10131                         string);
10132             }
10133         }
10134     }
10135     return cmailMsg;
10136 #endif /* WIN32 */
10137 }
10138
10139 void
10140 ResetGameEvent()
10141 {
10142     if (gameMode == Training)
10143       SetTrainingModeOff();
10144
10145     Reset(TRUE, TRUE);
10146     cmailMsgLoaded = FALSE;
10147     if (appData.icsActive) {
10148       SendToICS(ics_prefix);
10149       SendToICS("refresh\n");
10150     }
10151 }
10152
10153 void
10154 ExitEvent(status)
10155      int status;
10156 {
10157     exiting++;
10158     if (exiting > 2) {
10159       /* Give up on clean exit */
10160       exit(status);
10161     }
10162     if (exiting > 1) {
10163       /* Keep trying for clean exit */
10164       return;
10165     }
10166
10167     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10168
10169     if (telnetISR != NULL) {
10170       RemoveInputSource(telnetISR);
10171     }
10172     if (icsPR != NoProc) {
10173       DestroyChildProcess(icsPR, TRUE);
10174     }
10175
10176     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10177     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10178
10179     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10180     /* make sure this other one finishes before killing it!                  */
10181     if(endingGame) { int count = 0;
10182         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10183         while(endingGame && count++ < 10) DoSleep(1);
10184         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10185     }
10186
10187     /* Kill off chess programs */
10188     if (first.pr != NoProc) {
10189         ExitAnalyzeMode();
10190         
10191         DoSleep( appData.delayBeforeQuit );
10192         SendToProgram("quit\n", &first);
10193         DoSleep( appData.delayAfterQuit );
10194         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10195     }
10196     if (second.pr != NoProc) {
10197         DoSleep( appData.delayBeforeQuit );
10198         SendToProgram("quit\n", &second);
10199         DoSleep( appData.delayAfterQuit );
10200         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10201     }
10202     if (first.isr != NULL) {
10203         RemoveInputSource(first.isr);
10204     }
10205     if (second.isr != NULL) {
10206         RemoveInputSource(second.isr);
10207     }
10208
10209     ShutDownFrontEnd();
10210     exit(status);
10211 }
10212
10213 void
10214 PauseEvent()
10215 {
10216     if (appData.debugMode)
10217         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10218     if (pausing) {
10219         pausing = FALSE;
10220         ModeHighlight();
10221         if (gameMode == MachinePlaysWhite ||
10222             gameMode == MachinePlaysBlack) {
10223             StartClocks();
10224         } else {
10225             DisplayBothClocks();
10226         }
10227         if (gameMode == PlayFromGameFile) {
10228             if (appData.timeDelay >= 0) 
10229                 AutoPlayGameLoop();
10230         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10231             Reset(FALSE, TRUE);
10232             SendToICS(ics_prefix);
10233             SendToICS("refresh\n");
10234         } else if (currentMove < forwardMostMove) {
10235             ForwardInner(forwardMostMove);
10236         }
10237         pauseExamInvalid = FALSE;
10238     } else {
10239         switch (gameMode) {
10240           default:
10241             return;
10242           case IcsExamining:
10243             pauseExamForwardMostMove = forwardMostMove;
10244             pauseExamInvalid = FALSE;
10245             /* fall through */
10246           case IcsObserving:
10247           case IcsPlayingWhite:
10248           case IcsPlayingBlack:
10249             pausing = TRUE;
10250             ModeHighlight();
10251             return;
10252           case PlayFromGameFile:
10253             (void) StopLoadGameTimer();
10254             pausing = TRUE;
10255             ModeHighlight();
10256             break;
10257           case BeginningOfGame:
10258             if (appData.icsActive) return;
10259             /* else fall through */
10260           case MachinePlaysWhite:
10261           case MachinePlaysBlack:
10262           case TwoMachinesPlay:
10263             if (forwardMostMove == 0)
10264               return;           /* don't pause if no one has moved */
10265             if ((gameMode == MachinePlaysWhite &&
10266                  !WhiteOnMove(forwardMostMove)) ||
10267                 (gameMode == MachinePlaysBlack &&
10268                  WhiteOnMove(forwardMostMove))) {
10269                 StopClocks();
10270             }
10271             pausing = TRUE;
10272             ModeHighlight();
10273             break;
10274         }
10275     }
10276 }
10277
10278 void
10279 EditCommentEvent()
10280 {
10281     char title[MSG_SIZ];
10282
10283     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10284         strcpy(title, _("Edit comment"));
10285     } else {
10286         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10287                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10288                 parseList[currentMove - 1]);
10289     }
10290
10291     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10292 }
10293
10294
10295 void
10296 EditTagsEvent()
10297 {
10298     char *tags = PGNTags(&gameInfo);
10299     EditTagsPopUp(tags);
10300     free(tags);
10301 }
10302
10303 void
10304 AnalyzeModeEvent()
10305 {
10306     if (appData.noChessProgram || gameMode == AnalyzeMode)
10307       return;
10308
10309     if (gameMode != AnalyzeFile) {
10310         if (!appData.icsEngineAnalyze) {
10311                EditGameEvent();
10312                if (gameMode != EditGame) return;
10313         }
10314         ResurrectChessProgram();
10315         SendToProgram("analyze\n", &first);
10316         first.analyzing = TRUE;
10317         /*first.maybeThinking = TRUE;*/
10318         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10319         AnalysisPopUp(_("Analysis"),
10320                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10321     }
10322     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10323     pausing = FALSE;
10324     ModeHighlight();
10325     SetGameInfo();
10326
10327     StartAnalysisClock();
10328     GetTimeMark(&lastNodeCountTime);
10329     lastNodeCount = 0;
10330 }
10331
10332 void
10333 AnalyzeFileEvent()
10334 {
10335     if (appData.noChessProgram || gameMode == AnalyzeFile)
10336       return;
10337
10338     if (gameMode != AnalyzeMode) {
10339         EditGameEvent();
10340         if (gameMode != EditGame) return;
10341         ResurrectChessProgram();
10342         SendToProgram("analyze\n", &first);
10343         first.analyzing = TRUE;
10344         /*first.maybeThinking = TRUE;*/
10345         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10346         AnalysisPopUp(_("Analysis"),
10347                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10348     }
10349     gameMode = AnalyzeFile;
10350     pausing = FALSE;
10351     ModeHighlight();
10352     SetGameInfo();
10353
10354     StartAnalysisClock();
10355     GetTimeMark(&lastNodeCountTime);
10356     lastNodeCount = 0;
10357 }
10358
10359 void
10360 MachineWhiteEvent()
10361 {
10362     char buf[MSG_SIZ];
10363     char *bookHit = NULL;
10364
10365     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10366       return;
10367
10368
10369     if (gameMode == PlayFromGameFile || 
10370         gameMode == TwoMachinesPlay  || 
10371         gameMode == Training         || 
10372         gameMode == AnalyzeMode      || 
10373         gameMode == EndOfGame)
10374         EditGameEvent();
10375
10376     if (gameMode == EditPosition) 
10377         EditPositionDone();
10378
10379     if (!WhiteOnMove(currentMove)) {
10380         DisplayError(_("It is not White's turn"), 0);
10381         return;
10382     }
10383   
10384     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10385       ExitAnalyzeMode();
10386
10387     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10388         gameMode == AnalyzeFile)
10389         TruncateGame();
10390
10391     ResurrectChessProgram();    /* in case it isn't running */
10392     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10393         gameMode = MachinePlaysWhite;
10394         ResetClocks();
10395     } else
10396     gameMode = MachinePlaysWhite;
10397     pausing = FALSE;
10398     ModeHighlight();
10399     SetGameInfo();
10400     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10401     DisplayTitle(buf);
10402     if (first.sendName) {
10403       sprintf(buf, "name %s\n", gameInfo.black);
10404       SendToProgram(buf, &first);
10405     }
10406     if (first.sendTime) {
10407       if (first.useColors) {
10408         SendToProgram("black\n", &first); /*gnu kludge*/
10409       }
10410       SendTimeRemaining(&first, TRUE);
10411     }
10412     if (first.useColors) {
10413       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10414     }
10415     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10416     SetMachineThinkingEnables();
10417     first.maybeThinking = TRUE;
10418     StartClocks();
10419     firstMove = FALSE;
10420
10421     if (appData.autoFlipView && !flipView) {
10422       flipView = !flipView;
10423       DrawPosition(FALSE, NULL);
10424       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10425     }
10426
10427     if(bookHit) { // [HGM] book: simulate book reply
10428         static char bookMove[MSG_SIZ]; // a bit generous?
10429
10430         programStats.nodes = programStats.depth = programStats.time = 
10431         programStats.score = programStats.got_only_move = 0;
10432         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10433
10434         strcpy(bookMove, "move ");
10435         strcat(bookMove, bookHit);
10436         HandleMachineMove(bookMove, &first);
10437     }
10438 }
10439
10440 void
10441 MachineBlackEvent()
10442 {
10443     char buf[MSG_SIZ];
10444    char *bookHit = NULL;
10445
10446     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10447         return;
10448
10449
10450     if (gameMode == PlayFromGameFile || 
10451         gameMode == TwoMachinesPlay  || 
10452         gameMode == Training         || 
10453         gameMode == AnalyzeMode      || 
10454         gameMode == EndOfGame)
10455         EditGameEvent();
10456
10457     if (gameMode == EditPosition) 
10458         EditPositionDone();
10459
10460     if (WhiteOnMove(currentMove)) {
10461         DisplayError(_("It is not Black's turn"), 0);
10462         return;
10463     }
10464     
10465     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10466       ExitAnalyzeMode();
10467
10468     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10469         gameMode == AnalyzeFile)
10470         TruncateGame();
10471
10472     ResurrectChessProgram();    /* in case it isn't running */
10473     gameMode = MachinePlaysBlack;
10474     pausing = FALSE;
10475     ModeHighlight();
10476     SetGameInfo();
10477     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10478     DisplayTitle(buf);
10479     if (first.sendName) {
10480       sprintf(buf, "name %s\n", gameInfo.white);
10481       SendToProgram(buf, &first);
10482     }
10483     if (first.sendTime) {
10484       if (first.useColors) {
10485         SendToProgram("white\n", &first); /*gnu kludge*/
10486       }
10487       SendTimeRemaining(&first, FALSE);
10488     }
10489     if (first.useColors) {
10490       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10491     }
10492     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10493     SetMachineThinkingEnables();
10494     first.maybeThinking = TRUE;
10495     StartClocks();
10496
10497     if (appData.autoFlipView && flipView) {
10498       flipView = !flipView;
10499       DrawPosition(FALSE, NULL);
10500       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10501     }
10502     if(bookHit) { // [HGM] book: simulate book reply
10503         static char bookMove[MSG_SIZ]; // a bit generous?
10504
10505         programStats.nodes = programStats.depth = programStats.time = 
10506         programStats.score = programStats.got_only_move = 0;
10507         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10508
10509         strcpy(bookMove, "move ");
10510         strcat(bookMove, bookHit);
10511         HandleMachineMove(bookMove, &first);
10512     }
10513 }
10514
10515
10516 void
10517 DisplayTwoMachinesTitle()
10518 {
10519     char buf[MSG_SIZ];
10520     if (appData.matchGames > 0) {
10521         if (first.twoMachinesColor[0] == 'w') {
10522             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10523                     gameInfo.white, gameInfo.black,
10524                     first.matchWins, second.matchWins,
10525                     matchGame - 1 - (first.matchWins + second.matchWins));
10526         } else {
10527             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10528                     gameInfo.white, gameInfo.black,
10529                     second.matchWins, first.matchWins,
10530                     matchGame - 1 - (first.matchWins + second.matchWins));
10531         }
10532     } else {
10533         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10534     }
10535     DisplayTitle(buf);
10536 }
10537
10538 void
10539 TwoMachinesEvent P((void))
10540 {
10541     int i;
10542     char buf[MSG_SIZ];
10543     ChessProgramState *onmove;
10544     char *bookHit = NULL;
10545     
10546     if (appData.noChessProgram) return;
10547
10548     switch (gameMode) {
10549       case TwoMachinesPlay:
10550         return;
10551       case MachinePlaysWhite:
10552       case MachinePlaysBlack:
10553         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10554             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10555             return;
10556         }
10557         /* fall through */
10558       case BeginningOfGame:
10559       case PlayFromGameFile:
10560       case EndOfGame:
10561         EditGameEvent();
10562         if (gameMode != EditGame) return;
10563         break;
10564       case EditPosition:
10565         EditPositionDone();
10566         break;
10567       case AnalyzeMode:
10568       case AnalyzeFile:
10569         ExitAnalyzeMode();
10570         break;
10571       case EditGame:
10572       default:
10573         break;
10574     }
10575
10576     forwardMostMove = currentMove;
10577     ResurrectChessProgram();    /* in case first program isn't running */
10578
10579     if (second.pr == NULL) {
10580         StartChessProgram(&second);
10581         if (second.protocolVersion == 1) {
10582           TwoMachinesEventIfReady();
10583         } else {
10584           /* kludge: allow timeout for initial "feature" command */
10585           FreezeUI();
10586           DisplayMessage("", _("Starting second chess program"));
10587           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10588         }
10589         return;
10590     }
10591     DisplayMessage("", "");
10592     InitChessProgram(&second, FALSE);
10593     SendToProgram("force\n", &second);
10594     if (startedFromSetupPosition) {
10595         SendBoard(&second, backwardMostMove);
10596     if (appData.debugMode) {
10597         fprintf(debugFP, "Two Machines\n");
10598     }
10599     }
10600     for (i = backwardMostMove; i < forwardMostMove; i++) {
10601         SendMoveToProgram(i, &second);
10602     }
10603
10604     gameMode = TwoMachinesPlay;
10605     pausing = FALSE;
10606     ModeHighlight();
10607     SetGameInfo();
10608     DisplayTwoMachinesTitle();
10609     firstMove = TRUE;
10610     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10611         onmove = &first;
10612     } else {
10613         onmove = &second;
10614     }
10615
10616     SendToProgram(first.computerString, &first);
10617     if (first.sendName) {
10618       sprintf(buf, "name %s\n", second.tidy);
10619       SendToProgram(buf, &first);
10620     }
10621     SendToProgram(second.computerString, &second);
10622     if (second.sendName) {
10623       sprintf(buf, "name %s\n", first.tidy);
10624       SendToProgram(buf, &second);
10625     }
10626
10627     ResetClocks();
10628     if (!first.sendTime || !second.sendTime) {
10629         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10630         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10631     }
10632     if (onmove->sendTime) {
10633       if (onmove->useColors) {
10634         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10635       }
10636       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10637     }
10638     if (onmove->useColors) {
10639       SendToProgram(onmove->twoMachinesColor, onmove);
10640     }
10641     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10642 //    SendToProgram("go\n", onmove);
10643     onmove->maybeThinking = TRUE;
10644     SetMachineThinkingEnables();
10645
10646     StartClocks();
10647
10648     if(bookHit) { // [HGM] book: simulate book reply
10649         static char bookMove[MSG_SIZ]; // a bit generous?
10650
10651         programStats.nodes = programStats.depth = programStats.time = 
10652         programStats.score = programStats.got_only_move = 0;
10653         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10654
10655         strcpy(bookMove, "move ");
10656         strcat(bookMove, bookHit);
10657         HandleMachineMove(bookMove, &first);
10658     }
10659 }
10660
10661 void
10662 TrainingEvent()
10663 {
10664     if (gameMode == Training) {
10665       SetTrainingModeOff();
10666       gameMode = PlayFromGameFile;
10667       DisplayMessage("", _("Training mode off"));
10668     } else {
10669       gameMode = Training;
10670       animateTraining = appData.animate;
10671
10672       /* make sure we are not already at the end of the game */
10673       if (currentMove < forwardMostMove) {
10674         SetTrainingModeOn();
10675         DisplayMessage("", _("Training mode on"));
10676       } else {
10677         gameMode = PlayFromGameFile;
10678         DisplayError(_("Already at end of game"), 0);
10679       }
10680     }
10681     ModeHighlight();
10682 }
10683
10684 void
10685 IcsClientEvent()
10686 {
10687     if (!appData.icsActive) return;
10688     switch (gameMode) {
10689       case IcsPlayingWhite:
10690       case IcsPlayingBlack:
10691       case IcsObserving:
10692       case IcsIdle:
10693       case BeginningOfGame:
10694       case IcsExamining:
10695         return;
10696
10697       case EditGame:
10698         break;
10699
10700       case EditPosition:
10701         EditPositionDone();
10702         break;
10703
10704       case AnalyzeMode:
10705       case AnalyzeFile:
10706         ExitAnalyzeMode();
10707         break;
10708         
10709       default:
10710         EditGameEvent();
10711         break;
10712     }
10713
10714     gameMode = IcsIdle;
10715     ModeHighlight();
10716     return;
10717 }
10718
10719
10720 void
10721 EditGameEvent()
10722 {
10723     int i;
10724
10725     switch (gameMode) {
10726       case Training:
10727         SetTrainingModeOff();
10728         break;
10729       case MachinePlaysWhite:
10730       case MachinePlaysBlack:
10731       case BeginningOfGame:
10732         SendToProgram("force\n", &first);
10733         SetUserThinkingEnables();
10734         break;
10735       case PlayFromGameFile:
10736         (void) StopLoadGameTimer();
10737         if (gameFileFP != NULL) {
10738             gameFileFP = NULL;
10739         }
10740         break;
10741       case EditPosition:
10742         EditPositionDone();
10743         break;
10744       case AnalyzeMode:
10745       case AnalyzeFile:
10746         ExitAnalyzeMode();
10747         SendToProgram("force\n", &first);
10748         break;
10749       case TwoMachinesPlay:
10750         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10751         ResurrectChessProgram();
10752         SetUserThinkingEnables();
10753         break;
10754       case EndOfGame:
10755         ResurrectChessProgram();
10756         break;
10757       case IcsPlayingBlack:
10758       case IcsPlayingWhite:
10759         DisplayError(_("Warning: You are still playing a game"), 0);
10760         break;
10761       case IcsObserving:
10762         DisplayError(_("Warning: You are still observing a game"), 0);
10763         break;
10764       case IcsExamining:
10765         DisplayError(_("Warning: You are still examining a game"), 0);
10766         break;
10767       case IcsIdle:
10768         break;
10769       case EditGame:
10770       default:
10771         return;
10772     }
10773     
10774     pausing = FALSE;
10775     StopClocks();
10776     first.offeredDraw = second.offeredDraw = 0;
10777
10778     if (gameMode == PlayFromGameFile) {
10779         whiteTimeRemaining = timeRemaining[0][currentMove];
10780         blackTimeRemaining = timeRemaining[1][currentMove];
10781         DisplayTitle("");
10782     }
10783
10784     if (gameMode == MachinePlaysWhite ||
10785         gameMode == MachinePlaysBlack ||
10786         gameMode == TwoMachinesPlay ||
10787         gameMode == EndOfGame) {
10788         i = forwardMostMove;
10789         while (i > currentMove) {
10790             SendToProgram("undo\n", &first);
10791             i--;
10792         }
10793         whiteTimeRemaining = timeRemaining[0][currentMove];
10794         blackTimeRemaining = timeRemaining[1][currentMove];
10795         DisplayBothClocks();
10796         if (whiteFlag || blackFlag) {
10797             whiteFlag = blackFlag = 0;
10798         }
10799         DisplayTitle("");
10800     }           
10801     
10802     gameMode = EditGame;
10803     ModeHighlight();
10804     SetGameInfo();
10805 }
10806
10807
10808 void
10809 EditPositionEvent()
10810 {
10811     if (gameMode == EditPosition) {
10812         EditGameEvent();
10813         return;
10814     }
10815     
10816     EditGameEvent();
10817     if (gameMode != EditGame) return;
10818     
10819     gameMode = EditPosition;
10820     ModeHighlight();
10821     SetGameInfo();
10822     if (currentMove > 0)
10823       CopyBoard(boards[0], boards[currentMove]);
10824     
10825     blackPlaysFirst = !WhiteOnMove(currentMove);
10826     ResetClocks();
10827     currentMove = forwardMostMove = backwardMostMove = 0;
10828     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10829     DisplayMove(-1);
10830 }
10831
10832 void
10833 ExitAnalyzeMode()
10834 {
10835     /* [DM] icsEngineAnalyze - possible call from other functions */
10836     if (appData.icsEngineAnalyze) {
10837         appData.icsEngineAnalyze = FALSE;
10838
10839         DisplayMessage("",_("Close ICS engine analyze..."));
10840     }
10841     if (first.analysisSupport && first.analyzing) {
10842       SendToProgram("exit\n", &first);
10843       first.analyzing = FALSE;
10844     }
10845     AnalysisPopDown();
10846     thinkOutput[0] = NULLCHAR;
10847 }
10848
10849 void
10850 EditPositionDone()
10851 {
10852     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10853
10854     startedFromSetupPosition = TRUE;
10855     InitChessProgram(&first, FALSE);
10856     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10857     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10858         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10859         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10860     } else castlingRights[0][2] = -1;
10861     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10862         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10863         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10864     } else castlingRights[0][5] = -1;
10865     SendToProgram("force\n", &first);
10866     if (blackPlaysFirst) {
10867         strcpy(moveList[0], "");
10868         strcpy(parseList[0], "");
10869         currentMove = forwardMostMove = backwardMostMove = 1;
10870         CopyBoard(boards[1], boards[0]);
10871         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10872         { int i;
10873           epStatus[1] = epStatus[0];
10874           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10875         }
10876     } else {
10877         currentMove = forwardMostMove = backwardMostMove = 0;
10878     }
10879     SendBoard(&first, forwardMostMove);
10880     if (appData.debugMode) {
10881         fprintf(debugFP, "EditPosDone\n");
10882     }
10883     DisplayTitle("");
10884     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10885     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10886     gameMode = EditGame;
10887     ModeHighlight();
10888     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10889     ClearHighlights(); /* [AS] */
10890 }
10891
10892 /* Pause for `ms' milliseconds */
10893 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10894 void
10895 TimeDelay(ms)
10896      long ms;
10897 {
10898     TimeMark m1, m2;
10899
10900     GetTimeMark(&m1);
10901     do {
10902         GetTimeMark(&m2);
10903     } while (SubtractTimeMarks(&m2, &m1) < ms);
10904 }
10905
10906 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10907 void
10908 SendMultiLineToICS(buf)
10909      char *buf;
10910 {
10911     char temp[MSG_SIZ+1], *p;
10912     int len;
10913
10914     len = strlen(buf);
10915     if (len > MSG_SIZ)
10916       len = MSG_SIZ;
10917   
10918     strncpy(temp, buf, len);
10919     temp[len] = 0;
10920
10921     p = temp;
10922     while (*p) {
10923         if (*p == '\n' || *p == '\r')
10924           *p = ' ';
10925         ++p;
10926     }
10927
10928     strcat(temp, "\n");
10929     SendToICS(temp);
10930     SendToPlayer(temp, strlen(temp));
10931 }
10932
10933 void
10934 SetWhiteToPlayEvent()
10935 {
10936     if (gameMode == EditPosition) {
10937         blackPlaysFirst = FALSE;
10938         DisplayBothClocks();    /* works because currentMove is 0 */
10939     } else if (gameMode == IcsExamining) {
10940         SendToICS(ics_prefix);
10941         SendToICS("tomove white\n");
10942     }
10943 }
10944
10945 void
10946 SetBlackToPlayEvent()
10947 {
10948     if (gameMode == EditPosition) {
10949         blackPlaysFirst = TRUE;
10950         currentMove = 1;        /* kludge */
10951         DisplayBothClocks();
10952         currentMove = 0;
10953     } else if (gameMode == IcsExamining) {
10954         SendToICS(ics_prefix);
10955         SendToICS("tomove black\n");
10956     }
10957 }
10958
10959 void
10960 EditPositionMenuEvent(selection, x, y)
10961      ChessSquare selection;
10962      int x, y;
10963 {
10964     char buf[MSG_SIZ];
10965     ChessSquare piece = boards[0][y][x];
10966
10967     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10968
10969     switch (selection) {
10970       case ClearBoard:
10971         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10972             SendToICS(ics_prefix);
10973             SendToICS("bsetup clear\n");
10974         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10975             SendToICS(ics_prefix);
10976             SendToICS("clearboard\n");
10977         } else {
10978             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10979                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10980                 for (y = 0; y < BOARD_HEIGHT; y++) {
10981                     if (gameMode == IcsExamining) {
10982                         if (boards[currentMove][y][x] != EmptySquare) {
10983                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10984                                     AAA + x, ONE + y);
10985                             SendToICS(buf);
10986                         }
10987                     } else {
10988                         boards[0][y][x] = p;
10989                     }
10990                 }
10991             }
10992         }
10993         if (gameMode == EditPosition) {
10994             DrawPosition(FALSE, boards[0]);
10995         }
10996         break;
10997
10998       case WhitePlay:
10999         SetWhiteToPlayEvent();
11000         break;
11001
11002       case BlackPlay:
11003         SetBlackToPlayEvent();
11004         break;
11005
11006       case EmptySquare:
11007         if (gameMode == IcsExamining) {
11008             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11009             SendToICS(buf);
11010         } else {
11011             boards[0][y][x] = EmptySquare;
11012             DrawPosition(FALSE, boards[0]);
11013         }
11014         break;
11015
11016       case PromotePiece:
11017         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11018            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11019             selection = (ChessSquare) (PROMOTED piece);
11020         } else if(piece == EmptySquare) selection = WhiteSilver;
11021         else selection = (ChessSquare)((int)piece - 1);
11022         goto defaultlabel;
11023
11024       case DemotePiece:
11025         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11026            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11027             selection = (ChessSquare) (DEMOTED piece);
11028         } else if(piece == EmptySquare) selection = BlackSilver;
11029         else selection = (ChessSquare)((int)piece + 1);       
11030         goto defaultlabel;
11031
11032       case WhiteQueen:
11033       case BlackQueen:
11034         if(gameInfo.variant == VariantShatranj ||
11035            gameInfo.variant == VariantXiangqi  ||
11036            gameInfo.variant == VariantCourier    )
11037             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11038         goto defaultlabel;
11039
11040       case WhiteKing:
11041       case BlackKing:
11042         if(gameInfo.variant == VariantXiangqi)
11043             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11044         if(gameInfo.variant == VariantKnightmate)
11045             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11046       default:
11047         defaultlabel:
11048         if (gameMode == IcsExamining) {
11049             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11050                     PieceToChar(selection), AAA + x, ONE + y);
11051             SendToICS(buf);
11052         } else {
11053             boards[0][y][x] = selection;
11054             DrawPosition(FALSE, boards[0]);
11055         }
11056         break;
11057     }
11058 }
11059
11060
11061 void
11062 DropMenuEvent(selection, x, y)
11063      ChessSquare selection;
11064      int x, y;
11065 {
11066     ChessMove moveType;
11067
11068     switch (gameMode) {
11069       case IcsPlayingWhite:
11070       case MachinePlaysBlack:
11071         if (!WhiteOnMove(currentMove)) {
11072             DisplayMoveError(_("It is Black's turn"));
11073             return;
11074         }
11075         moveType = WhiteDrop;
11076         break;
11077       case IcsPlayingBlack:
11078       case MachinePlaysWhite:
11079         if (WhiteOnMove(currentMove)) {
11080             DisplayMoveError(_("It is White's turn"));
11081             return;
11082         }
11083         moveType = BlackDrop;
11084         break;
11085       case EditGame:
11086         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11087         break;
11088       default:
11089         return;
11090     }
11091
11092     if (moveType == BlackDrop && selection < BlackPawn) {
11093       selection = (ChessSquare) ((int) selection
11094                                  + (int) BlackPawn - (int) WhitePawn);
11095     }
11096     if (boards[currentMove][y][x] != EmptySquare) {
11097         DisplayMoveError(_("That square is occupied"));
11098         return;
11099     }
11100
11101     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11102 }
11103
11104 void
11105 AcceptEvent()
11106 {
11107     /* Accept a pending offer of any kind from opponent */
11108     
11109     if (appData.icsActive) {
11110         SendToICS(ics_prefix);
11111         SendToICS("accept\n");
11112     } else if (cmailMsgLoaded) {
11113         if (currentMove == cmailOldMove &&
11114             commentList[cmailOldMove] != NULL &&
11115             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11116                    "Black offers a draw" : "White offers a draw")) {
11117             TruncateGame();
11118             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11119             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11120         } else {
11121             DisplayError(_("There is no pending offer on this move"), 0);
11122             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11123         }
11124     } else {
11125         /* Not used for offers from chess program */
11126     }
11127 }
11128
11129 void
11130 DeclineEvent()
11131 {
11132     /* Decline a pending offer of any kind from opponent */
11133     
11134     if (appData.icsActive) {
11135         SendToICS(ics_prefix);
11136         SendToICS("decline\n");
11137     } else if (cmailMsgLoaded) {
11138         if (currentMove == cmailOldMove &&
11139             commentList[cmailOldMove] != NULL &&
11140             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11141                    "Black offers a draw" : "White offers a draw")) {
11142 #ifdef NOTDEF
11143             AppendComment(cmailOldMove, "Draw declined");
11144             DisplayComment(cmailOldMove - 1, "Draw declined");
11145 #endif /*NOTDEF*/
11146         } else {
11147             DisplayError(_("There is no pending offer on this move"), 0);
11148         }
11149     } else {
11150         /* Not used for offers from chess program */
11151     }
11152 }
11153
11154 void
11155 RematchEvent()
11156 {
11157     /* Issue ICS rematch command */
11158     if (appData.icsActive) {
11159         SendToICS(ics_prefix);
11160         SendToICS("rematch\n");
11161     }
11162 }
11163
11164 void
11165 CallFlagEvent()
11166 {
11167     /* Call your opponent's flag (claim a win on time) */
11168     if (appData.icsActive) {
11169         SendToICS(ics_prefix);
11170         SendToICS("flag\n");
11171     } else {
11172         switch (gameMode) {
11173           default:
11174             return;
11175           case MachinePlaysWhite:
11176             if (whiteFlag) {
11177                 if (blackFlag)
11178                   GameEnds(GameIsDrawn, "Both players ran out of time",
11179                            GE_PLAYER);
11180                 else
11181                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11182             } else {
11183                 DisplayError(_("Your opponent is not out of time"), 0);
11184             }
11185             break;
11186           case MachinePlaysBlack:
11187             if (blackFlag) {
11188                 if (whiteFlag)
11189                   GameEnds(GameIsDrawn, "Both players ran out of time",
11190                            GE_PLAYER);
11191                 else
11192                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11193             } else {
11194                 DisplayError(_("Your opponent is not out of time"), 0);
11195             }
11196             break;
11197         }
11198     }
11199 }
11200
11201 void
11202 DrawEvent()
11203 {
11204     /* Offer draw or accept pending draw offer from opponent */
11205     
11206     if (appData.icsActive) {
11207         /* Note: tournament rules require draw offers to be
11208            made after you make your move but before you punch
11209            your clock.  Currently ICS doesn't let you do that;
11210            instead, you immediately punch your clock after making
11211            a move, but you can offer a draw at any time. */
11212         
11213         SendToICS(ics_prefix);
11214         SendToICS("draw\n");
11215     } else if (cmailMsgLoaded) {
11216         if (currentMove == cmailOldMove &&
11217             commentList[cmailOldMove] != NULL &&
11218             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11219                    "Black offers a draw" : "White offers a draw")) {
11220             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11221             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11222         } else if (currentMove == cmailOldMove + 1) {
11223             char *offer = WhiteOnMove(cmailOldMove) ?
11224               "White offers a draw" : "Black offers a draw";
11225             AppendComment(currentMove, offer);
11226             DisplayComment(currentMove - 1, offer);
11227             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11228         } else {
11229             DisplayError(_("You must make your move before offering a draw"), 0);
11230             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11231         }
11232     } else if (first.offeredDraw) {
11233         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11234     } else {
11235         if (first.sendDrawOffers) {
11236             SendToProgram("draw\n", &first);
11237             userOfferedDraw = TRUE;
11238         }
11239     }
11240 }
11241
11242 void
11243 AdjournEvent()
11244 {
11245     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11246     
11247     if (appData.icsActive) {
11248         SendToICS(ics_prefix);
11249         SendToICS("adjourn\n");
11250     } else {
11251         /* Currently GNU Chess doesn't offer or accept Adjourns */
11252     }
11253 }
11254
11255
11256 void
11257 AbortEvent()
11258 {
11259     /* Offer Abort or accept pending Abort offer from opponent */
11260     
11261     if (appData.icsActive) {
11262         SendToICS(ics_prefix);
11263         SendToICS("abort\n");
11264     } else {
11265         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11266     }
11267 }
11268
11269 void
11270 ResignEvent()
11271 {
11272     /* Resign.  You can do this even if it's not your turn. */
11273     
11274     if (appData.icsActive) {
11275         SendToICS(ics_prefix);
11276         SendToICS("resign\n");
11277     } else {
11278         switch (gameMode) {
11279           case MachinePlaysWhite:
11280             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11281             break;
11282           case MachinePlaysBlack:
11283             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11284             break;
11285           case EditGame:
11286             if (cmailMsgLoaded) {
11287                 TruncateGame();
11288                 if (WhiteOnMove(cmailOldMove)) {
11289                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11290                 } else {
11291                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11292                 }
11293                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11294             }
11295             break;
11296           default:
11297             break;
11298         }
11299     }
11300 }
11301
11302
11303 void
11304 StopObservingEvent()
11305 {
11306     /* Stop observing current games */
11307     SendToICS(ics_prefix);
11308     SendToICS("unobserve\n");
11309 }
11310
11311 void
11312 StopExaminingEvent()
11313 {
11314     /* Stop observing current game */
11315     SendToICS(ics_prefix);
11316     SendToICS("unexamine\n");
11317 }
11318
11319 void
11320 ForwardInner(target)
11321      int target;
11322 {
11323     int limit;
11324
11325     if (appData.debugMode)
11326         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11327                 target, currentMove, forwardMostMove);
11328
11329     if (gameMode == EditPosition)
11330       return;
11331
11332     if (gameMode == PlayFromGameFile && !pausing)
11333       PauseEvent();
11334     
11335     if (gameMode == IcsExamining && pausing)
11336       limit = pauseExamForwardMostMove;
11337     else
11338       limit = forwardMostMove;
11339     
11340     if (target > limit) target = limit;
11341
11342     if (target > 0 && moveList[target - 1][0]) {
11343         int fromX, fromY, toX, toY;
11344         toX = moveList[target - 1][2] - AAA;
11345         toY = moveList[target - 1][3] - ONE;
11346         if (moveList[target - 1][1] == '@') {
11347             if (appData.highlightLastMove) {
11348                 SetHighlights(-1, -1, toX, toY);
11349             }
11350         } else {
11351             fromX = moveList[target - 1][0] - AAA;
11352             fromY = moveList[target - 1][1] - ONE;
11353             if (target == currentMove + 1) {
11354                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11355             }
11356             if (appData.highlightLastMove) {
11357                 SetHighlights(fromX, fromY, toX, toY);
11358             }
11359         }
11360     }
11361     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11362         gameMode == Training || gameMode == PlayFromGameFile || 
11363         gameMode == AnalyzeFile) {
11364         while (currentMove < target) {
11365             SendMoveToProgram(currentMove++, &first);
11366         }
11367     } else {
11368         currentMove = target;
11369     }
11370     
11371     if (gameMode == EditGame || gameMode == EndOfGame) {
11372         whiteTimeRemaining = timeRemaining[0][currentMove];
11373         blackTimeRemaining = timeRemaining[1][currentMove];
11374     }
11375     DisplayBothClocks();
11376     DisplayMove(currentMove - 1);
11377     DrawPosition(FALSE, boards[currentMove]);
11378     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11379     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11380         DisplayComment(currentMove - 1, commentList[currentMove]);
11381     }
11382 }
11383
11384
11385 void
11386 ForwardEvent()
11387 {
11388     if (gameMode == IcsExamining && !pausing) {
11389         SendToICS(ics_prefix);
11390         SendToICS("forward\n");
11391     } else {
11392         ForwardInner(currentMove + 1);
11393     }
11394 }
11395
11396 void
11397 ToEndEvent()
11398 {
11399     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11400         /* to optimze, we temporarily turn off analysis mode while we feed
11401          * the remaining moves to the engine. Otherwise we get analysis output
11402          * after each move.
11403          */ 
11404         if (first.analysisSupport) {
11405           SendToProgram("exit\nforce\n", &first);
11406           first.analyzing = FALSE;
11407         }
11408     }
11409         
11410     if (gameMode == IcsExamining && !pausing) {
11411         SendToICS(ics_prefix);
11412         SendToICS("forward 999999\n");
11413     } else {
11414         ForwardInner(forwardMostMove);
11415     }
11416
11417     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11418         /* we have fed all the moves, so reactivate analysis mode */
11419         SendToProgram("analyze\n", &first);
11420         first.analyzing = TRUE;
11421         /*first.maybeThinking = TRUE;*/
11422         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11423     }
11424 }
11425
11426 void
11427 BackwardInner(target)
11428      int target;
11429 {
11430     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11431
11432     if (appData.debugMode)
11433         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11434                 target, currentMove, forwardMostMove);
11435
11436     if (gameMode == EditPosition) return;
11437     if (currentMove <= backwardMostMove) {
11438         ClearHighlights();
11439         DrawPosition(full_redraw, boards[currentMove]);
11440         return;
11441     }
11442     if (gameMode == PlayFromGameFile && !pausing)
11443       PauseEvent();
11444     
11445     if (moveList[target][0]) {
11446         int fromX, fromY, toX, toY;
11447         toX = moveList[target][2] - AAA;
11448         toY = moveList[target][3] - ONE;
11449         if (moveList[target][1] == '@') {
11450             if (appData.highlightLastMove) {
11451                 SetHighlights(-1, -1, toX, toY);
11452             }
11453         } else {
11454             fromX = moveList[target][0] - AAA;
11455             fromY = moveList[target][1] - ONE;
11456             if (target == currentMove - 1) {
11457                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11458             }
11459             if (appData.highlightLastMove) {
11460                 SetHighlights(fromX, fromY, toX, toY);
11461             }
11462         }
11463     }
11464     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11465         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11466         while (currentMove > target) {
11467             SendToProgram("undo\n", &first);
11468             currentMove--;
11469         }
11470     } else {
11471         currentMove = target;
11472     }
11473     
11474     if (gameMode == EditGame || gameMode == EndOfGame) {
11475         whiteTimeRemaining = timeRemaining[0][currentMove];
11476         blackTimeRemaining = timeRemaining[1][currentMove];
11477     }
11478     DisplayBothClocks();
11479     DisplayMove(currentMove - 1);
11480     DrawPosition(full_redraw, boards[currentMove]);
11481     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11482     // [HGM] PV info: routine tests if comment empty
11483     DisplayComment(currentMove - 1, commentList[currentMove]);
11484 }
11485
11486 void
11487 BackwardEvent()
11488 {
11489     if (gameMode == IcsExamining && !pausing) {
11490         SendToICS(ics_prefix);
11491         SendToICS("backward\n");
11492     } else {
11493         BackwardInner(currentMove - 1);
11494     }
11495 }
11496
11497 void
11498 ToStartEvent()
11499 {
11500     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11501         /* to optimze, we temporarily turn off analysis mode while we undo
11502          * all the moves. Otherwise we get analysis output after each undo.
11503          */ 
11504         if (first.analysisSupport) {
11505           SendToProgram("exit\nforce\n", &first);
11506           first.analyzing = FALSE;
11507         }
11508     }
11509
11510     if (gameMode == IcsExamining && !pausing) {
11511         SendToICS(ics_prefix);
11512         SendToICS("backward 999999\n");
11513     } else {
11514         BackwardInner(backwardMostMove);
11515     }
11516
11517     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11518         /* we have fed all the moves, so reactivate analysis mode */
11519         SendToProgram("analyze\n", &first);
11520         first.analyzing = TRUE;
11521         /*first.maybeThinking = TRUE;*/
11522         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11523     }
11524 }
11525
11526 void
11527 ToNrEvent(int to)
11528 {
11529   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11530   if (to >= forwardMostMove) to = forwardMostMove;
11531   if (to <= backwardMostMove) to = backwardMostMove;
11532   if (to < currentMove) {
11533     BackwardInner(to);
11534   } else {
11535     ForwardInner(to);
11536   }
11537 }
11538
11539 void
11540 RevertEvent()
11541 {
11542     if (gameMode != IcsExamining) {
11543         DisplayError(_("You are not examining a game"), 0);
11544         return;
11545     }
11546     if (pausing) {
11547         DisplayError(_("You can't revert while pausing"), 0);
11548         return;
11549     }
11550     SendToICS(ics_prefix);
11551     SendToICS("revert\n");
11552 }
11553
11554 void
11555 RetractMoveEvent()
11556 {
11557     switch (gameMode) {
11558       case MachinePlaysWhite:
11559       case MachinePlaysBlack:
11560         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11561             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11562             return;
11563         }
11564         if (forwardMostMove < 2) return;
11565         currentMove = forwardMostMove = forwardMostMove - 2;
11566         whiteTimeRemaining = timeRemaining[0][currentMove];
11567         blackTimeRemaining = timeRemaining[1][currentMove];
11568         DisplayBothClocks();
11569         DisplayMove(currentMove - 1);
11570         ClearHighlights();/*!! could figure this out*/
11571         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11572         SendToProgram("remove\n", &first);
11573         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11574         break;
11575
11576       case BeginningOfGame:
11577       default:
11578         break;
11579
11580       case IcsPlayingWhite:
11581       case IcsPlayingBlack:
11582         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11583             SendToICS(ics_prefix);
11584             SendToICS("takeback 2\n");
11585         } else {
11586             SendToICS(ics_prefix);
11587             SendToICS("takeback 1\n");
11588         }
11589         break;
11590     }
11591 }
11592
11593 void
11594 MoveNowEvent()
11595 {
11596     ChessProgramState *cps;
11597
11598     switch (gameMode) {
11599       case MachinePlaysWhite:
11600         if (!WhiteOnMove(forwardMostMove)) {
11601             DisplayError(_("It is your turn"), 0);
11602             return;
11603         }
11604         cps = &first;
11605         break;
11606       case MachinePlaysBlack:
11607         if (WhiteOnMove(forwardMostMove)) {
11608             DisplayError(_("It is your turn"), 0);
11609             return;
11610         }
11611         cps = &first;
11612         break;
11613       case TwoMachinesPlay:
11614         if (WhiteOnMove(forwardMostMove) ==
11615             (first.twoMachinesColor[0] == 'w')) {
11616             cps = &first;
11617         } else {
11618             cps = &second;
11619         }
11620         break;
11621       case BeginningOfGame:
11622       default:
11623         return;
11624     }
11625     SendToProgram("?\n", cps);
11626 }
11627
11628 void
11629 TruncateGameEvent()
11630 {
11631     EditGameEvent();
11632     if (gameMode != EditGame) return;
11633     TruncateGame();
11634 }
11635
11636 void
11637 TruncateGame()
11638 {
11639     if (forwardMostMove > currentMove) {
11640         if (gameInfo.resultDetails != NULL) {
11641             free(gameInfo.resultDetails);
11642             gameInfo.resultDetails = NULL;
11643             gameInfo.result = GameUnfinished;
11644         }
11645         forwardMostMove = currentMove;
11646         HistorySet(parseList, backwardMostMove, forwardMostMove,
11647                    currentMove-1);
11648     }
11649 }
11650
11651 void
11652 HintEvent()
11653 {
11654     if (appData.noChessProgram) return;
11655     switch (gameMode) {
11656       case MachinePlaysWhite:
11657         if (WhiteOnMove(forwardMostMove)) {
11658             DisplayError(_("Wait until your turn"), 0);
11659             return;
11660         }
11661         break;
11662       case BeginningOfGame:
11663       case MachinePlaysBlack:
11664         if (!WhiteOnMove(forwardMostMove)) {
11665             DisplayError(_("Wait until your turn"), 0);
11666             return;
11667         }
11668         break;
11669       default:
11670         DisplayError(_("No hint available"), 0);
11671         return;
11672     }
11673     SendToProgram("hint\n", &first);
11674     hintRequested = TRUE;
11675 }
11676
11677 void
11678 BookEvent()
11679 {
11680     if (appData.noChessProgram) return;
11681     switch (gameMode) {
11682       case MachinePlaysWhite:
11683         if (WhiteOnMove(forwardMostMove)) {
11684             DisplayError(_("Wait until your turn"), 0);
11685             return;
11686         }
11687         break;
11688       case BeginningOfGame:
11689       case MachinePlaysBlack:
11690         if (!WhiteOnMove(forwardMostMove)) {
11691             DisplayError(_("Wait until your turn"), 0);
11692             return;
11693         }
11694         break;
11695       case EditPosition:
11696         EditPositionDone();
11697         break;
11698       case TwoMachinesPlay:
11699         return;
11700       default:
11701         break;
11702     }
11703     SendToProgram("bk\n", &first);
11704     bookOutput[0] = NULLCHAR;
11705     bookRequested = TRUE;
11706 }
11707
11708 void
11709 AboutGameEvent()
11710 {
11711     char *tags = PGNTags(&gameInfo);
11712     TagsPopUp(tags, CmailMsg());
11713     free(tags);
11714 }
11715
11716 /* end button procedures */
11717
11718 void
11719 PrintPosition(fp, move)
11720      FILE *fp;
11721      int move;
11722 {
11723     int i, j;
11724     
11725     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11726         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11727             char c = PieceToChar(boards[move][i][j]);
11728             fputc(c == 'x' ? '.' : c, fp);
11729             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11730         }
11731     }
11732     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11733       fprintf(fp, "white to play\n");
11734     else
11735       fprintf(fp, "black to play\n");
11736 }
11737
11738 void
11739 PrintOpponents(fp)
11740      FILE *fp;
11741 {
11742     if (gameInfo.white != NULL) {
11743         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11744     } else {
11745         fprintf(fp, "\n");
11746     }
11747 }
11748
11749 /* Find last component of program's own name, using some heuristics */
11750 void
11751 TidyProgramName(prog, host, buf)
11752      char *prog, *host, buf[MSG_SIZ];
11753 {
11754     char *p, *q;
11755     int local = (strcmp(host, "localhost") == 0);
11756     while (!local && (p = strchr(prog, ';')) != NULL) {
11757         p++;
11758         while (*p == ' ') p++;
11759         prog = p;
11760     }
11761     if (*prog == '"' || *prog == '\'') {
11762         q = strchr(prog + 1, *prog);
11763     } else {
11764         q = strchr(prog, ' ');
11765     }
11766     if (q == NULL) q = prog + strlen(prog);
11767     p = q;
11768     while (p >= prog && *p != '/' && *p != '\\') p--;
11769     p++;
11770     if(p == prog && *p == '"') p++;
11771     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11772     memcpy(buf, p, q - p);
11773     buf[q - p] = NULLCHAR;
11774     if (!local) {
11775         strcat(buf, "@");
11776         strcat(buf, host);
11777     }
11778 }
11779
11780 char *
11781 TimeControlTagValue()
11782 {
11783     char buf[MSG_SIZ];
11784     if (!appData.clockMode) {
11785         strcpy(buf, "-");
11786     } else if (movesPerSession > 0) {
11787         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11788     } else if (timeIncrement == 0) {
11789         sprintf(buf, "%ld", timeControl/1000);
11790     } else {
11791         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11792     }
11793     return StrSave(buf);
11794 }
11795
11796 void
11797 SetGameInfo()
11798 {
11799     /* This routine is used only for certain modes */
11800     VariantClass v = gameInfo.variant;
11801     ClearGameInfo(&gameInfo);
11802     gameInfo.variant = v;
11803
11804     switch (gameMode) {
11805       case MachinePlaysWhite:
11806         gameInfo.event = StrSave( appData.pgnEventHeader );
11807         gameInfo.site = StrSave(HostName());
11808         gameInfo.date = PGNDate();
11809         gameInfo.round = StrSave("-");
11810         gameInfo.white = StrSave(first.tidy);
11811         gameInfo.black = StrSave(UserName());
11812         gameInfo.timeControl = TimeControlTagValue();
11813         break;
11814
11815       case MachinePlaysBlack:
11816         gameInfo.event = StrSave( appData.pgnEventHeader );
11817         gameInfo.site = StrSave(HostName());
11818         gameInfo.date = PGNDate();
11819         gameInfo.round = StrSave("-");
11820         gameInfo.white = StrSave(UserName());
11821         gameInfo.black = StrSave(first.tidy);
11822         gameInfo.timeControl = TimeControlTagValue();
11823         break;
11824
11825       case TwoMachinesPlay:
11826         gameInfo.event = StrSave( appData.pgnEventHeader );
11827         gameInfo.site = StrSave(HostName());
11828         gameInfo.date = PGNDate();
11829         if (matchGame > 0) {
11830             char buf[MSG_SIZ];
11831             sprintf(buf, "%d", matchGame);
11832             gameInfo.round = StrSave(buf);
11833         } else {
11834             gameInfo.round = StrSave("-");
11835         }
11836         if (first.twoMachinesColor[0] == 'w') {
11837             gameInfo.white = StrSave(first.tidy);
11838             gameInfo.black = StrSave(second.tidy);
11839         } else {
11840             gameInfo.white = StrSave(second.tidy);
11841             gameInfo.black = StrSave(first.tidy);
11842         }
11843         gameInfo.timeControl = TimeControlTagValue();
11844         break;
11845
11846       case EditGame:
11847         gameInfo.event = StrSave("Edited game");
11848         gameInfo.site = StrSave(HostName());
11849         gameInfo.date = PGNDate();
11850         gameInfo.round = StrSave("-");
11851         gameInfo.white = StrSave("-");
11852         gameInfo.black = StrSave("-");
11853         break;
11854
11855       case EditPosition:
11856         gameInfo.event = StrSave("Edited position");
11857         gameInfo.site = StrSave(HostName());
11858         gameInfo.date = PGNDate();
11859         gameInfo.round = StrSave("-");
11860         gameInfo.white = StrSave("-");
11861         gameInfo.black = StrSave("-");
11862         break;
11863
11864       case IcsPlayingWhite:
11865       case IcsPlayingBlack:
11866       case IcsObserving:
11867       case IcsExamining:
11868         break;
11869
11870       case PlayFromGameFile:
11871         gameInfo.event = StrSave("Game from non-PGN file");
11872         gameInfo.site = StrSave(HostName());
11873         gameInfo.date = PGNDate();
11874         gameInfo.round = StrSave("-");
11875         gameInfo.white = StrSave("?");
11876         gameInfo.black = StrSave("?");
11877         break;
11878
11879       default:
11880         break;
11881     }
11882 }
11883
11884 void
11885 ReplaceComment(index, text)
11886      int index;
11887      char *text;
11888 {
11889     int len;
11890
11891     while (*text == '\n') text++;
11892     len = strlen(text);
11893     while (len > 0 && text[len - 1] == '\n') len--;
11894
11895     if (commentList[index] != NULL)
11896       free(commentList[index]);
11897
11898     if (len == 0) {
11899         commentList[index] = NULL;
11900         return;
11901     }
11902     commentList[index] = (char *) malloc(len + 2);
11903     strncpy(commentList[index], text, len);
11904     commentList[index][len] = '\n';
11905     commentList[index][len + 1] = NULLCHAR;
11906 }
11907
11908 void
11909 CrushCRs(text)
11910      char *text;
11911 {
11912   char *p = text;
11913   char *q = text;
11914   char ch;
11915
11916   do {
11917     ch = *p++;
11918     if (ch == '\r') continue;
11919     *q++ = ch;
11920   } while (ch != '\0');
11921 }
11922
11923 void
11924 AppendComment(index, text)
11925      int index;
11926      char *text;
11927 {
11928     int oldlen, len;
11929     char *old;
11930
11931     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11932
11933     CrushCRs(text);
11934     while (*text == '\n') text++;
11935     len = strlen(text);
11936     while (len > 0 && text[len - 1] == '\n') len--;
11937
11938     if (len == 0) return;
11939
11940     if (commentList[index] != NULL) {
11941         old = commentList[index];
11942         oldlen = strlen(old);
11943         commentList[index] = (char *) malloc(oldlen + len + 2);
11944         strcpy(commentList[index], old);
11945         free(old);
11946         strncpy(&commentList[index][oldlen], text, len);
11947         commentList[index][oldlen + len] = '\n';
11948         commentList[index][oldlen + len + 1] = NULLCHAR;
11949     } else {
11950         commentList[index] = (char *) malloc(len + 2);
11951         strncpy(commentList[index], text, len);
11952         commentList[index][len] = '\n';
11953         commentList[index][len + 1] = NULLCHAR;
11954     }
11955 }
11956
11957 static char * FindStr( char * text, char * sub_text )
11958 {
11959     char * result = strstr( text, sub_text );
11960
11961     if( result != NULL ) {
11962         result += strlen( sub_text );
11963     }
11964
11965     return result;
11966 }
11967
11968 /* [AS] Try to extract PV info from PGN comment */
11969 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11970 char *GetInfoFromComment( int index, char * text )
11971 {
11972     char * sep = text;
11973
11974     if( text != NULL && index > 0 ) {
11975         int score = 0;
11976         int depth = 0;
11977         int time = -1, sec = 0, deci;
11978         char * s_eval = FindStr( text, "[%eval " );
11979         char * s_emt = FindStr( text, "[%emt " );
11980
11981         if( s_eval != NULL || s_emt != NULL ) {
11982             /* New style */
11983             char delim;
11984
11985             if( s_eval != NULL ) {
11986                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11987                     return text;
11988                 }
11989
11990                 if( delim != ']' ) {
11991                     return text;
11992                 }
11993             }
11994
11995             if( s_emt != NULL ) {
11996             }
11997         }
11998         else {
11999             /* We expect something like: [+|-]nnn.nn/dd */
12000             int score_lo = 0;
12001
12002             sep = strchr( text, '/' );
12003             if( sep == NULL || sep < (text+4) ) {
12004                 return text;
12005             }
12006
12007             time = -1; sec = -1; deci = -1;
12008             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12009                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12010                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12011                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12012                 return text;
12013             }
12014
12015             if( score_lo < 0 || score_lo >= 100 ) {
12016                 return text;
12017             }
12018
12019             if(sec >= 0) time = 600*time + 10*sec; else
12020             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12021
12022             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12023
12024             /* [HGM] PV time: now locate end of PV info */
12025             while( *++sep >= '0' && *sep <= '9'); // strip depth
12026             if(time >= 0)
12027             while( *++sep >= '0' && *sep <= '9'); // strip time
12028             if(sec >= 0)
12029             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12030             if(deci >= 0)
12031             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12032             while(*sep == ' ') sep++;
12033         }
12034
12035         if( depth <= 0 ) {
12036             return text;
12037         }
12038
12039         if( time < 0 ) {
12040             time = -1;
12041         }
12042
12043         pvInfoList[index-1].depth = depth;
12044         pvInfoList[index-1].score = score;
12045         pvInfoList[index-1].time  = 10*time; // centi-sec
12046     }
12047     return sep;
12048 }
12049
12050 void
12051 SendToProgram(message, cps)
12052      char *message;
12053      ChessProgramState *cps;
12054 {
12055     int count, outCount, error;
12056     char buf[MSG_SIZ];
12057
12058     if (cps->pr == NULL) return;
12059     Attention(cps);
12060     
12061     if (appData.debugMode) {
12062         TimeMark now;
12063         GetTimeMark(&now);
12064         fprintf(debugFP, "%ld >%-6s: %s", 
12065                 SubtractTimeMarks(&now, &programStartTime),
12066                 cps->which, message);
12067     }
12068     
12069     count = strlen(message);
12070     outCount = OutputToProcess(cps->pr, message, count, &error);
12071     if (outCount < count && !exiting 
12072                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12073         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12074         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12075             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12076                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12077                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12078             } else {
12079                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12080             }
12081             gameInfo.resultDetails = buf;
12082         }
12083         DisplayFatalError(buf, error, 1);
12084     }
12085 }
12086
12087 void
12088 ReceiveFromProgram(isr, closure, message, count, error)
12089      InputSourceRef isr;
12090      VOIDSTAR closure;
12091      char *message;
12092      int count;
12093      int error;
12094 {
12095     char *end_str;
12096     char buf[MSG_SIZ];
12097     ChessProgramState *cps = (ChessProgramState *)closure;
12098
12099     if (isr != cps->isr) return; /* Killed intentionally */
12100     if (count <= 0) {
12101         if (count == 0) {
12102             sprintf(buf,
12103                     _("Error: %s chess program (%s) exited unexpectedly"),
12104                     cps->which, cps->program);
12105         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12106                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12107                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12108                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12109                 } else {
12110                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12111                 }
12112                 gameInfo.resultDetails = buf;
12113             }
12114             RemoveInputSource(cps->isr);
12115             DisplayFatalError(buf, 0, 1);
12116         } else {
12117             sprintf(buf,
12118                     _("Error reading from %s chess program (%s)"),
12119                     cps->which, cps->program);
12120             RemoveInputSource(cps->isr);
12121
12122             /* [AS] Program is misbehaving badly... kill it */
12123             if( count == -2 ) {
12124                 DestroyChildProcess( cps->pr, 9 );
12125                 cps->pr = NoProc;
12126             }
12127
12128             DisplayFatalError(buf, error, 1);
12129         }
12130         return;
12131     }
12132     
12133     if ((end_str = strchr(message, '\r')) != NULL)
12134       *end_str = NULLCHAR;
12135     if ((end_str = strchr(message, '\n')) != NULL)
12136       *end_str = NULLCHAR;
12137     
12138     if (appData.debugMode) {
12139         TimeMark now; int print = 1;
12140         char *quote = ""; char c; int i;
12141
12142         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12143                 char start = message[0];
12144                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12145                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12146                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12147                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12148                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12149                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12150                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12151                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12152                         { quote = "# "; print = (appData.engineComments == 2); }
12153                 message[0] = start; // restore original message
12154         }
12155         if(print) {
12156                 GetTimeMark(&now);
12157                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12158                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12159                         quote,
12160                         message);
12161         }
12162     }
12163
12164     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12165     if (appData.icsEngineAnalyze) {
12166         if (strstr(message, "whisper") != NULL ||
12167              strstr(message, "kibitz") != NULL || 
12168             strstr(message, "tellics") != NULL) return;
12169     }
12170
12171     HandleMachineMove(message, cps);
12172 }
12173
12174
12175 void
12176 SendTimeControl(cps, mps, tc, inc, sd, st)
12177      ChessProgramState *cps;
12178      int mps, inc, sd, st;
12179      long tc;
12180 {
12181     char buf[MSG_SIZ];
12182     int seconds;
12183
12184     if( timeControl_2 > 0 ) {
12185         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12186             tc = timeControl_2;
12187         }
12188     }
12189     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12190     inc /= cps->timeOdds;
12191     st  /= cps->timeOdds;
12192
12193     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12194
12195     if (st > 0) {
12196       /* Set exact time per move, normally using st command */
12197       if (cps->stKludge) {
12198         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12199         seconds = st % 60;
12200         if (seconds == 0) {
12201           sprintf(buf, "level 1 %d\n", st/60);
12202         } else {
12203           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12204         }
12205       } else {
12206         sprintf(buf, "st %d\n", st);
12207       }
12208     } else {
12209       /* Set conventional or incremental time control, using level command */
12210       if (seconds == 0) {
12211         /* Note old gnuchess bug -- minutes:seconds used to not work.
12212            Fixed in later versions, but still avoid :seconds
12213            when seconds is 0. */
12214         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12215       } else {
12216         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12217                 seconds, inc/1000);
12218       }
12219     }
12220     SendToProgram(buf, cps);
12221
12222     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12223     /* Orthogonally, limit search to given depth */
12224     if (sd > 0) {
12225       if (cps->sdKludge) {
12226         sprintf(buf, "depth\n%d\n", sd);
12227       } else {
12228         sprintf(buf, "sd %d\n", sd);
12229       }
12230       SendToProgram(buf, cps);
12231     }
12232
12233     if(cps->nps > 0) { /* [HGM] nps */
12234         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12235         else {
12236                 sprintf(buf, "nps %d\n", cps->nps);
12237               SendToProgram(buf, cps);
12238         }
12239     }
12240 }
12241
12242 ChessProgramState *WhitePlayer()
12243 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12244 {
12245     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12246        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12247         return &second;
12248     return &first;
12249 }
12250
12251 void
12252 SendTimeRemaining(cps, machineWhite)
12253      ChessProgramState *cps;
12254      int /*boolean*/ machineWhite;
12255 {
12256     char message[MSG_SIZ];
12257     long time, otime;
12258
12259     /* Note: this routine must be called when the clocks are stopped
12260        or when they have *just* been set or switched; otherwise
12261        it will be off by the time since the current tick started.
12262     */
12263     if (machineWhite) {
12264         time = whiteTimeRemaining / 10;
12265         otime = blackTimeRemaining / 10;
12266     } else {
12267         time = blackTimeRemaining / 10;
12268         otime = whiteTimeRemaining / 10;
12269     }
12270     /* [HGM] translate opponent's time by time-odds factor */
12271     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12272     if (appData.debugMode) {
12273         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12274     }
12275
12276     if (time <= 0) time = 1;
12277     if (otime <= 0) otime = 1;
12278     
12279     sprintf(message, "time %ld\n", time);
12280     SendToProgram(message, cps);
12281
12282     sprintf(message, "otim %ld\n", otime);
12283     SendToProgram(message, cps);
12284 }
12285
12286 int
12287 BoolFeature(p, name, loc, cps)
12288      char **p;
12289      char *name;
12290      int *loc;
12291      ChessProgramState *cps;
12292 {
12293   char buf[MSG_SIZ];
12294   int len = strlen(name);
12295   int val;
12296   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12297     (*p) += len + 1;
12298     sscanf(*p, "%d", &val);
12299     *loc = (val != 0);
12300     while (**p && **p != ' ') (*p)++;
12301     sprintf(buf, "accepted %s\n", name);
12302     SendToProgram(buf, cps);
12303     return TRUE;
12304   }
12305   return FALSE;
12306 }
12307
12308 int
12309 IntFeature(p, name, loc, cps)
12310      char **p;
12311      char *name;
12312      int *loc;
12313      ChessProgramState *cps;
12314 {
12315   char buf[MSG_SIZ];
12316   int len = strlen(name);
12317   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12318     (*p) += len + 1;
12319     sscanf(*p, "%d", loc);
12320     while (**p && **p != ' ') (*p)++;
12321     sprintf(buf, "accepted %s\n", name);
12322     SendToProgram(buf, cps);
12323     return TRUE;
12324   }
12325   return FALSE;
12326 }
12327
12328 int
12329 StringFeature(p, name, loc, cps)
12330      char **p;
12331      char *name;
12332      char loc[];
12333      ChessProgramState *cps;
12334 {
12335   char buf[MSG_SIZ];
12336   int len = strlen(name);
12337   if (strncmp((*p), name, len) == 0
12338       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12339     (*p) += len + 2;
12340     sscanf(*p, "%[^\"]", loc);
12341     while (**p && **p != '\"') (*p)++;
12342     if (**p == '\"') (*p)++;
12343     sprintf(buf, "accepted %s\n", name);
12344     SendToProgram(buf, cps);
12345     return TRUE;
12346   }
12347   return FALSE;
12348 }
12349
12350 int 
12351 ParseOption(Option *opt, ChessProgramState *cps)
12352 // [HGM] options: process the string that defines an engine option, and determine
12353 // name, type, default value, and allowed value range
12354 {
12355         char *p, *q, buf[MSG_SIZ];
12356         int n, min = (-1)<<31, max = 1<<31, def;
12357
12358         if(p = strstr(opt->name, " -spin ")) {
12359             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12360             if(max < min) max = min; // enforce consistency
12361             if(def < min) def = min;
12362             if(def > max) def = max;
12363             opt->value = def;
12364             opt->min = min;
12365             opt->max = max;
12366             opt->type = Spin;
12367         } else if((p = strstr(opt->name, " -slider "))) {
12368             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12369             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12370             if(max < min) max = min; // enforce consistency
12371             if(def < min) def = min;
12372             if(def > max) def = max;
12373             opt->value = def;
12374             opt->min = min;
12375             opt->max = max;
12376             opt->type = Spin; // Slider;
12377         } else if((p = strstr(opt->name, " -string "))) {
12378             opt->textValue = p+9;
12379             opt->type = TextBox;
12380         } else if((p = strstr(opt->name, " -file "))) {
12381             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12382             opt->textValue = p+7;
12383             opt->type = TextBox; // FileName;
12384         } else if((p = strstr(opt->name, " -path "))) {
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; // PathName;
12388         } else if(p = strstr(opt->name, " -check ")) {
12389             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12390             opt->value = (def != 0);
12391             opt->type = CheckBox;
12392         } else if(p = strstr(opt->name, " -combo ")) {
12393             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12394             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12395             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12396             opt->value = n = 0;
12397             while(q = StrStr(q, " /// ")) {
12398                 n++; *q = 0;    // count choices, and null-terminate each of them
12399                 q += 5;
12400                 if(*q == '*') { // remember default, which is marked with * prefix
12401                     q++;
12402                     opt->value = n;
12403                 }
12404                 cps->comboList[cps->comboCnt++] = q;
12405             }
12406             cps->comboList[cps->comboCnt++] = NULL;
12407             opt->max = n + 1;
12408             opt->type = ComboBox;
12409         } else if(p = strstr(opt->name, " -button")) {
12410             opt->type = Button;
12411         } else if(p = strstr(opt->name, " -save")) {
12412             opt->type = SaveButton;
12413         } else return FALSE;
12414         *p = 0; // terminate option name
12415         // now look if the command-line options define a setting for this engine option.
12416         if(cps->optionSettings && cps->optionSettings[0])
12417             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12418         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12419                 sprintf(buf, "option %s", p);
12420                 if(p = strstr(buf, ",")) *p = 0;
12421                 strcat(buf, "\n");
12422                 SendToProgram(buf, cps);
12423         }
12424         return TRUE;
12425 }
12426
12427 void
12428 FeatureDone(cps, val)
12429      ChessProgramState* cps;
12430      int val;
12431 {
12432   DelayedEventCallback cb = GetDelayedEvent();
12433   if ((cb == InitBackEnd3 && cps == &first) ||
12434       (cb == TwoMachinesEventIfReady && cps == &second)) {
12435     CancelDelayedEvent();
12436     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12437   }
12438   cps->initDone = val;
12439 }
12440
12441 /* Parse feature command from engine */
12442 void
12443 ParseFeatures(args, cps)
12444      char* args;
12445      ChessProgramState *cps;  
12446 {
12447   char *p = args;
12448   char *q;
12449   int val;
12450   char buf[MSG_SIZ];
12451
12452   for (;;) {
12453     while (*p == ' ') p++;
12454     if (*p == NULLCHAR) return;
12455
12456     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12457     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12458     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12459     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12460     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12461     if (BoolFeature(&p, "reuse", &val, cps)) {
12462       /* Engine can disable reuse, but can't enable it if user said no */
12463       if (!val) cps->reuse = FALSE;
12464       continue;
12465     }
12466     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12467     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12468       if (gameMode == TwoMachinesPlay) {
12469         DisplayTwoMachinesTitle();
12470       } else {
12471         DisplayTitle("");
12472       }
12473       continue;
12474     }
12475     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12476     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12477     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12478     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12479     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12480     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12481     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12482     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12483     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12484     if (IntFeature(&p, "done", &val, cps)) {
12485       FeatureDone(cps, val);
12486       continue;
12487     }
12488     /* Added by Tord: */
12489     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12490     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12491     /* End of additions by Tord */
12492
12493     /* [HGM] added features: */
12494     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12495     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12496     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12497     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12498     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12499     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12500     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12501         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12502             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12503             SendToProgram(buf, cps);
12504             continue;
12505         }
12506         if(cps->nrOptions >= MAX_OPTIONS) {
12507             cps->nrOptions--;
12508             sprintf(buf, "%s engine has too many options\n", cps->which);
12509             DisplayError(buf, 0);
12510         }
12511         continue;
12512     }
12513     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12514     /* End of additions by HGM */
12515
12516     /* unknown feature: complain and skip */
12517     q = p;
12518     while (*q && *q != '=') q++;
12519     sprintf(buf, "rejected %.*s\n", q-p, p);
12520     SendToProgram(buf, cps);
12521     p = q;
12522     if (*p == '=') {
12523       p++;
12524       if (*p == '\"') {
12525         p++;
12526         while (*p && *p != '\"') p++;
12527         if (*p == '\"') p++;
12528       } else {
12529         while (*p && *p != ' ') p++;
12530       }
12531     }
12532   }
12533
12534 }
12535
12536 void
12537 PeriodicUpdatesEvent(newState)
12538      int newState;
12539 {
12540     if (newState == appData.periodicUpdates)
12541       return;
12542
12543     appData.periodicUpdates=newState;
12544
12545     /* Display type changes, so update it now */
12546     DisplayAnalysis();
12547
12548     /* Get the ball rolling again... */
12549     if (newState) {
12550         AnalysisPeriodicEvent(1);
12551         StartAnalysisClock();
12552     }
12553 }
12554
12555 void
12556 PonderNextMoveEvent(newState)
12557      int newState;
12558 {
12559     if (newState == appData.ponderNextMove) return;
12560     if (gameMode == EditPosition) EditPositionDone();
12561     if (newState) {
12562         SendToProgram("hard\n", &first);
12563         if (gameMode == TwoMachinesPlay) {
12564             SendToProgram("hard\n", &second);
12565         }
12566     } else {
12567         SendToProgram("easy\n", &first);
12568         thinkOutput[0] = NULLCHAR;
12569         if (gameMode == TwoMachinesPlay) {
12570             SendToProgram("easy\n", &second);
12571         }
12572     }
12573     appData.ponderNextMove = newState;
12574 }
12575
12576 void
12577 NewSettingEvent(option, command, value)
12578      char *command;
12579      int option, value;
12580 {
12581     char buf[MSG_SIZ];
12582
12583     if (gameMode == EditPosition) EditPositionDone();
12584     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12585     SendToProgram(buf, &first);
12586     if (gameMode == TwoMachinesPlay) {
12587         SendToProgram(buf, &second);
12588     }
12589 }
12590
12591 void
12592 ShowThinkingEvent()
12593 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12594 {
12595     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12596     int newState = appData.showThinking
12597         // [HGM] thinking: other features now need thinking output as well
12598         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12599     
12600     if (oldState == newState) return;
12601     oldState = newState;
12602     if (gameMode == EditPosition) EditPositionDone();
12603     if (oldState) {
12604         SendToProgram("post\n", &first);
12605         if (gameMode == TwoMachinesPlay) {
12606             SendToProgram("post\n", &second);
12607         }
12608     } else {
12609         SendToProgram("nopost\n", &first);
12610         thinkOutput[0] = NULLCHAR;
12611         if (gameMode == TwoMachinesPlay) {
12612             SendToProgram("nopost\n", &second);
12613         }
12614     }
12615 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12616 }
12617
12618 void
12619 AskQuestionEvent(title, question, replyPrefix, which)
12620      char *title; char *question; char *replyPrefix; char *which;
12621 {
12622   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12623   if (pr == NoProc) return;
12624   AskQuestion(title, question, replyPrefix, pr);
12625 }
12626
12627 void
12628 DisplayMove(moveNumber)
12629      int moveNumber;
12630 {
12631     char message[MSG_SIZ];
12632     char res[MSG_SIZ];
12633     char cpThinkOutput[MSG_SIZ];
12634
12635     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12636     
12637     if (moveNumber == forwardMostMove - 1 || 
12638         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12639
12640         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12641
12642         if (strchr(cpThinkOutput, '\n')) {
12643             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12644         }
12645     } else {
12646         *cpThinkOutput = NULLCHAR;
12647     }
12648
12649     /* [AS] Hide thinking from human user */
12650     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12651         *cpThinkOutput = NULLCHAR;
12652         if( thinkOutput[0] != NULLCHAR ) {
12653             int i;
12654
12655             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12656                 cpThinkOutput[i] = '.';
12657             }
12658             cpThinkOutput[i] = NULLCHAR;
12659             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12660         }
12661     }
12662
12663     if (moveNumber == forwardMostMove - 1 &&
12664         gameInfo.resultDetails != NULL) {
12665         if (gameInfo.resultDetails[0] == NULLCHAR) {
12666             sprintf(res, " %s", PGNResult(gameInfo.result));
12667         } else {
12668             sprintf(res, " {%s} %s",
12669                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12670         }
12671     } else {
12672         res[0] = NULLCHAR;
12673     }
12674
12675     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12676         DisplayMessage(res, cpThinkOutput);
12677     } else {
12678         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12679                 WhiteOnMove(moveNumber) ? " " : ".. ",
12680                 parseList[moveNumber], res);
12681         DisplayMessage(message, cpThinkOutput);
12682     }
12683 }
12684
12685 void
12686 DisplayAnalysisText(text)
12687      char *text;
12688 {
12689     char buf[MSG_SIZ];
12690
12691     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12692                || appData.icsEngineAnalyze) {
12693         sprintf(buf, "Analysis (%s)", first.tidy);
12694         AnalysisPopUp(buf, text);
12695     }
12696 }
12697
12698 static int
12699 only_one_move(str)
12700      char *str;
12701 {
12702     while (*str && isspace(*str)) ++str;
12703     while (*str && !isspace(*str)) ++str;
12704     if (!*str) return 1;
12705     while (*str && isspace(*str)) ++str;
12706     if (!*str) return 1;
12707     return 0;
12708 }
12709
12710 void
12711 DisplayAnalysis()
12712 {
12713     char buf[MSG_SIZ];
12714     char lst[MSG_SIZ / 2];
12715     double nps;
12716     static char *xtra[] = { "", " (--)", " (++)" };
12717     int h, m, s, cs;
12718   
12719     if (programStats.time == 0) {
12720         programStats.time = 1;
12721     }
12722   
12723     if (programStats.got_only_move) {
12724         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12725     } else {
12726         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12727
12728         nps = (u64ToDouble(programStats.nodes) /
12729              ((double)programStats.time /100.0));
12730
12731         cs = programStats.time % 100;
12732         s = programStats.time / 100;
12733         h = (s / (60*60));
12734         s = s - h*60*60;
12735         m = (s/60);
12736         s = s - m*60;
12737
12738         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12739           if (programStats.move_name[0] != NULLCHAR) {
12740             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12741                     programStats.depth,
12742                     programStats.nr_moves-programStats.moves_left,
12743                     programStats.nr_moves, programStats.move_name,
12744                     ((float)programStats.score)/100.0, lst,
12745                     only_one_move(lst)?
12746                     xtra[programStats.got_fail] : "",
12747                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12748           } else {
12749             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12750                     programStats.depth,
12751                     programStats.nr_moves-programStats.moves_left,
12752                     programStats.nr_moves, ((float)programStats.score)/100.0,
12753                     lst,
12754                     only_one_move(lst)?
12755                     xtra[programStats.got_fail] : "",
12756                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12757           }
12758         } else {
12759             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12760                     programStats.depth,
12761                     ((float)programStats.score)/100.0,
12762                     lst,
12763                     only_one_move(lst)?
12764                     xtra[programStats.got_fail] : "",
12765                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12766         }
12767     }
12768     DisplayAnalysisText(buf);
12769 }
12770
12771 void
12772 DisplayComment(moveNumber, text)
12773      int moveNumber;
12774      char *text;
12775 {
12776     char title[MSG_SIZ];
12777     char buf[8000]; // comment can be long!
12778     int score, depth;
12779
12780     if( appData.autoDisplayComment ) {
12781         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12782             strcpy(title, "Comment");
12783         } else {
12784             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12785                     WhiteOnMove(moveNumber) ? " " : ".. ",
12786                     parseList[moveNumber]);
12787         }
12788         // [HGM] PV info: display PV info together with (or as) comment
12789         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12790             if(text == NULL) text = "";                                           
12791             score = pvInfoList[moveNumber].score;
12792             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12793                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12794             text = buf;
12795         }
12796     } else title[0] = 0;
12797
12798     if (text != NULL)
12799         CommentPopUp(title, text);
12800 }
12801
12802 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12803  * might be busy thinking or pondering.  It can be omitted if your
12804  * gnuchess is configured to stop thinking immediately on any user
12805  * input.  However, that gnuchess feature depends on the FIONREAD
12806  * ioctl, which does not work properly on some flavors of Unix.
12807  */
12808 void
12809 Attention(cps)
12810      ChessProgramState *cps;
12811 {
12812 #if ATTENTION
12813     if (!cps->useSigint) return;
12814     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12815     switch (gameMode) {
12816       case MachinePlaysWhite:
12817       case MachinePlaysBlack:
12818       case TwoMachinesPlay:
12819       case IcsPlayingWhite:
12820       case IcsPlayingBlack:
12821       case AnalyzeMode:
12822       case AnalyzeFile:
12823         /* Skip if we know it isn't thinking */
12824         if (!cps->maybeThinking) return;
12825         if (appData.debugMode)
12826           fprintf(debugFP, "Interrupting %s\n", cps->which);
12827         InterruptChildProcess(cps->pr);
12828         cps->maybeThinking = FALSE;
12829         break;
12830       default:
12831         break;
12832     }
12833 #endif /*ATTENTION*/
12834 }
12835
12836 int
12837 CheckFlags()
12838 {
12839     if (whiteTimeRemaining <= 0) {
12840         if (!whiteFlag) {
12841             whiteFlag = TRUE;
12842             if (appData.icsActive) {
12843                 if (appData.autoCallFlag &&
12844                     gameMode == IcsPlayingBlack && !blackFlag) {
12845                   SendToICS(ics_prefix);
12846                   SendToICS("flag\n");
12847                 }
12848             } else {
12849                 if (blackFlag) {
12850                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12851                 } else {
12852                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12853                     if (appData.autoCallFlag) {
12854                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12855                         return TRUE;
12856                     }
12857                 }
12858             }
12859         }
12860     }
12861     if (blackTimeRemaining <= 0) {
12862         if (!blackFlag) {
12863             blackFlag = TRUE;
12864             if (appData.icsActive) {
12865                 if (appData.autoCallFlag &&
12866                     gameMode == IcsPlayingWhite && !whiteFlag) {
12867                   SendToICS(ics_prefix);
12868                   SendToICS("flag\n");
12869                 }
12870             } else {
12871                 if (whiteFlag) {
12872                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12873                 } else {
12874                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12875                     if (appData.autoCallFlag) {
12876                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12877                         return TRUE;
12878                     }
12879                 }
12880             }
12881         }
12882     }
12883     return FALSE;
12884 }
12885
12886 void
12887 CheckTimeControl()
12888 {
12889     if (!appData.clockMode || appData.icsActive ||
12890         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12891
12892     /*
12893      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12894      */
12895     if ( !WhiteOnMove(forwardMostMove) )
12896         /* White made time control */
12897         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12898         /* [HGM] time odds: correct new time quota for time odds! */
12899                                             / WhitePlayer()->timeOdds;
12900       else
12901         /* Black made time control */
12902         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12903                                             / WhitePlayer()->other->timeOdds;
12904 }
12905
12906 void
12907 DisplayBothClocks()
12908 {
12909     int wom = gameMode == EditPosition ?
12910       !blackPlaysFirst : WhiteOnMove(currentMove);
12911     DisplayWhiteClock(whiteTimeRemaining, wom);
12912     DisplayBlackClock(blackTimeRemaining, !wom);
12913 }
12914
12915
12916 /* Timekeeping seems to be a portability nightmare.  I think everyone
12917    has ftime(), but I'm really not sure, so I'm including some ifdefs
12918    to use other calls if you don't.  Clocks will be less accurate if
12919    you have neither ftime nor gettimeofday.
12920 */
12921
12922 /* VS 2008 requires the #include outside of the function */
12923 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12924 #include <sys/timeb.h>
12925 #endif
12926
12927 /* Get the current time as a TimeMark */
12928 void
12929 GetTimeMark(tm)
12930      TimeMark *tm;
12931 {
12932 #if HAVE_GETTIMEOFDAY
12933
12934     struct timeval timeVal;
12935     struct timezone timeZone;
12936
12937     gettimeofday(&timeVal, &timeZone);
12938     tm->sec = (long) timeVal.tv_sec; 
12939     tm->ms = (int) (timeVal.tv_usec / 1000L);
12940
12941 #else /*!HAVE_GETTIMEOFDAY*/
12942 #if HAVE_FTIME
12943
12944 // include <sys/timeb.h> / moved to just above start of function
12945     struct timeb timeB;
12946
12947     ftime(&timeB);
12948     tm->sec = (long) timeB.time;
12949     tm->ms = (int) timeB.millitm;
12950
12951 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12952     tm->sec = (long) time(NULL);
12953     tm->ms = 0;
12954 #endif
12955 #endif
12956 }
12957
12958 /* Return the difference in milliseconds between two
12959    time marks.  We assume the difference will fit in a long!
12960 */
12961 long
12962 SubtractTimeMarks(tm2, tm1)
12963      TimeMark *tm2, *tm1;
12964 {
12965     return 1000L*(tm2->sec - tm1->sec) +
12966            (long) (tm2->ms - tm1->ms);
12967 }
12968
12969
12970 /*
12971  * Code to manage the game clocks.
12972  *
12973  * In tournament play, black starts the clock and then white makes a move.
12974  * We give the human user a slight advantage if he is playing white---the
12975  * clocks don't run until he makes his first move, so it takes zero time.
12976  * Also, we don't account for network lag, so we could get out of sync
12977  * with GNU Chess's clock -- but then, referees are always right.  
12978  */
12979
12980 static TimeMark tickStartTM;
12981 static long intendedTickLength;
12982
12983 long
12984 NextTickLength(timeRemaining)
12985      long timeRemaining;
12986 {
12987     long nominalTickLength, nextTickLength;
12988
12989     if (timeRemaining > 0L && timeRemaining <= 10000L)
12990       nominalTickLength = 100L;
12991     else
12992       nominalTickLength = 1000L;
12993     nextTickLength = timeRemaining % nominalTickLength;
12994     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12995
12996     return nextTickLength;
12997 }
12998
12999 /* Adjust clock one minute up or down */
13000 void
13001 AdjustClock(Boolean which, int dir)
13002 {
13003     if(which) blackTimeRemaining += 60000*dir;
13004     else      whiteTimeRemaining += 60000*dir;
13005     DisplayBothClocks();
13006 }
13007
13008 /* Stop clocks and reset to a fresh time control */
13009 void
13010 ResetClocks() 
13011 {
13012     (void) StopClockTimer();
13013     if (appData.icsActive) {
13014         whiteTimeRemaining = blackTimeRemaining = 0;
13015     } else { /* [HGM] correct new time quote for time odds */
13016         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13017         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13018     }
13019     if (whiteFlag || blackFlag) {
13020         DisplayTitle("");
13021         whiteFlag = blackFlag = FALSE;
13022     }
13023     DisplayBothClocks();
13024 }
13025
13026 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13027
13028 /* Decrement running clock by amount of time that has passed */
13029 void
13030 DecrementClocks()
13031 {
13032     long timeRemaining;
13033     long lastTickLength, fudge;
13034     TimeMark now;
13035
13036     if (!appData.clockMode) return;
13037     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13038         
13039     GetTimeMark(&now);
13040
13041     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13042
13043     /* Fudge if we woke up a little too soon */
13044     fudge = intendedTickLength - lastTickLength;
13045     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13046
13047     if (WhiteOnMove(forwardMostMove)) {
13048         if(whiteNPS >= 0) lastTickLength = 0;
13049         timeRemaining = whiteTimeRemaining -= lastTickLength;
13050         DisplayWhiteClock(whiteTimeRemaining - fudge,
13051                           WhiteOnMove(currentMove));
13052     } else {
13053         if(blackNPS >= 0) lastTickLength = 0;
13054         timeRemaining = blackTimeRemaining -= lastTickLength;
13055         DisplayBlackClock(blackTimeRemaining - fudge,
13056                           !WhiteOnMove(currentMove));
13057     }
13058
13059     if (CheckFlags()) return;
13060         
13061     tickStartTM = now;
13062     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13063     StartClockTimer(intendedTickLength);
13064
13065     /* if the time remaining has fallen below the alarm threshold, sound the
13066      * alarm. if the alarm has sounded and (due to a takeback or time control
13067      * with increment) the time remaining has increased to a level above the
13068      * threshold, reset the alarm so it can sound again. 
13069      */
13070     
13071     if (appData.icsActive && appData.icsAlarm) {
13072
13073         /* make sure we are dealing with the user's clock */
13074         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13075                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13076            )) return;
13077
13078         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13079             alarmSounded = FALSE;
13080         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13081             PlayAlarmSound();
13082             alarmSounded = TRUE;
13083         }
13084     }
13085 }
13086
13087
13088 /* A player has just moved, so stop the previously running
13089    clock and (if in clock mode) start the other one.
13090    We redisplay both clocks in case we're in ICS mode, because
13091    ICS gives us an update to both clocks after every move.
13092    Note that this routine is called *after* forwardMostMove
13093    is updated, so the last fractional tick must be subtracted
13094    from the color that is *not* on move now.
13095 */
13096 void
13097 SwitchClocks()
13098 {
13099     long lastTickLength;
13100     TimeMark now;
13101     int flagged = FALSE;
13102
13103     GetTimeMark(&now);
13104
13105     if (StopClockTimer() && appData.clockMode) {
13106         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13107         if (WhiteOnMove(forwardMostMove)) {
13108             if(blackNPS >= 0) lastTickLength = 0;
13109             blackTimeRemaining -= lastTickLength;
13110            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13111 //         if(pvInfoList[forwardMostMove-1].time == -1)
13112                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13113                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13114         } else {
13115            if(whiteNPS >= 0) lastTickLength = 0;
13116            whiteTimeRemaining -= lastTickLength;
13117            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13118 //         if(pvInfoList[forwardMostMove-1].time == -1)
13119                  pvInfoList[forwardMostMove-1].time = 
13120                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13121         }
13122         flagged = CheckFlags();
13123     }
13124     CheckTimeControl();
13125
13126     if (flagged || !appData.clockMode) return;
13127
13128     switch (gameMode) {
13129       case MachinePlaysBlack:
13130       case MachinePlaysWhite:
13131       case BeginningOfGame:
13132         if (pausing) return;
13133         break;
13134
13135       case EditGame:
13136       case PlayFromGameFile:
13137       case IcsExamining:
13138         return;
13139
13140       default:
13141         break;
13142     }
13143
13144     tickStartTM = now;
13145     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13146       whiteTimeRemaining : blackTimeRemaining);
13147     StartClockTimer(intendedTickLength);
13148 }
13149         
13150
13151 /* Stop both clocks */
13152 void
13153 StopClocks()
13154 {       
13155     long lastTickLength;
13156     TimeMark now;
13157
13158     if (!StopClockTimer()) return;
13159     if (!appData.clockMode) return;
13160
13161     GetTimeMark(&now);
13162
13163     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13164     if (WhiteOnMove(forwardMostMove)) {
13165         if(whiteNPS >= 0) lastTickLength = 0;
13166         whiteTimeRemaining -= lastTickLength;
13167         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13168     } else {
13169         if(blackNPS >= 0) lastTickLength = 0;
13170         blackTimeRemaining -= lastTickLength;
13171         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13172     }
13173     CheckFlags();
13174 }
13175         
13176 /* Start clock of player on move.  Time may have been reset, so
13177    if clock is already running, stop and restart it. */
13178 void
13179 StartClocks()
13180 {
13181     (void) StopClockTimer(); /* in case it was running already */
13182     DisplayBothClocks();
13183     if (CheckFlags()) return;
13184
13185     if (!appData.clockMode) return;
13186     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13187
13188     GetTimeMark(&tickStartTM);
13189     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13190       whiteTimeRemaining : blackTimeRemaining);
13191
13192    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13193     whiteNPS = blackNPS = -1; 
13194     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13195        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13196         whiteNPS = first.nps;
13197     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13198        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13199         blackNPS = first.nps;
13200     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13201         whiteNPS = second.nps;
13202     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13203         blackNPS = second.nps;
13204     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13205
13206     StartClockTimer(intendedTickLength);
13207 }
13208
13209 char *
13210 TimeString(ms)
13211      long ms;
13212 {
13213     long second, minute, hour, day;
13214     char *sign = "";
13215     static char buf[32];
13216     
13217     if (ms > 0 && ms <= 9900) {
13218       /* convert milliseconds to tenths, rounding up */
13219       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13220
13221       sprintf(buf, " %03.1f ", tenths/10.0);
13222       return buf;
13223     }
13224
13225     /* convert milliseconds to seconds, rounding up */
13226     /* use floating point to avoid strangeness of integer division
13227        with negative dividends on many machines */
13228     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13229
13230     if (second < 0) {
13231         sign = "-";
13232         second = -second;
13233     }
13234     
13235     day = second / (60 * 60 * 24);
13236     second = second % (60 * 60 * 24);
13237     hour = second / (60 * 60);
13238     second = second % (60 * 60);
13239     minute = second / 60;
13240     second = second % 60;
13241     
13242     if (day > 0)
13243       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13244               sign, day, hour, minute, second);
13245     else if (hour > 0)
13246       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13247     else
13248       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13249     
13250     return buf;
13251 }
13252
13253
13254 /*
13255  * This is necessary because some C libraries aren't ANSI C compliant yet.
13256  */
13257 char *
13258 StrStr(string, match)
13259      char *string, *match;
13260 {
13261     int i, length;
13262     
13263     length = strlen(match);
13264     
13265     for (i = strlen(string) - length; i >= 0; i--, string++)
13266       if (!strncmp(match, string, length))
13267         return string;
13268     
13269     return NULL;
13270 }
13271
13272 char *
13273 StrCaseStr(string, match)
13274      char *string, *match;
13275 {
13276     int i, j, length;
13277     
13278     length = strlen(match);
13279     
13280     for (i = strlen(string) - length; i >= 0; i--, string++) {
13281         for (j = 0; j < length; j++) {
13282             if (ToLower(match[j]) != ToLower(string[j]))
13283               break;
13284         }
13285         if (j == length) return string;
13286     }
13287
13288     return NULL;
13289 }
13290
13291 #ifndef _amigados
13292 int
13293 StrCaseCmp(s1, s2)
13294      char *s1, *s2;
13295 {
13296     char c1, c2;
13297     
13298     for (;;) {
13299         c1 = ToLower(*s1++);
13300         c2 = ToLower(*s2++);
13301         if (c1 > c2) return 1;
13302         if (c1 < c2) return -1;
13303         if (c1 == NULLCHAR) return 0;
13304     }
13305 }
13306
13307
13308 int
13309 ToLower(c)
13310      int c;
13311 {
13312     return isupper(c) ? tolower(c) : c;
13313 }
13314
13315
13316 int
13317 ToUpper(c)
13318      int c;
13319 {
13320     return islower(c) ? toupper(c) : c;
13321 }
13322 #endif /* !_amigados    */
13323
13324 char *
13325 StrSave(s)
13326      char *s;
13327 {
13328     char *ret;
13329
13330     if ((ret = (char *) malloc(strlen(s) + 1))) {
13331         strcpy(ret, s);
13332     }
13333     return ret;
13334 }
13335
13336 char *
13337 StrSavePtr(s, savePtr)
13338      char *s, **savePtr;
13339 {
13340     if (*savePtr) {
13341         free(*savePtr);
13342     }
13343     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13344         strcpy(*savePtr, s);
13345     }
13346     return(*savePtr);
13347 }
13348
13349 char *
13350 PGNDate()
13351 {
13352     time_t clock;
13353     struct tm *tm;
13354     char buf[MSG_SIZ];
13355
13356     clock = time((time_t *)NULL);
13357     tm = localtime(&clock);
13358     sprintf(buf, "%04d.%02d.%02d",
13359             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13360     return StrSave(buf);
13361 }
13362
13363
13364 char *
13365 PositionToFEN(move, overrideCastling)
13366      int move;
13367      char *overrideCastling;
13368 {
13369     int i, j, fromX, fromY, toX, toY;
13370     int whiteToPlay;
13371     char buf[128];
13372     char *p, *q;
13373     int emptycount;
13374     ChessSquare piece;
13375
13376     whiteToPlay = (gameMode == EditPosition) ?
13377       !blackPlaysFirst : (move % 2 == 0);
13378     p = buf;
13379
13380     /* Piece placement data */
13381     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13382         emptycount = 0;
13383         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13384             if (boards[move][i][j] == EmptySquare) {
13385                 emptycount++;
13386             } else { ChessSquare piece = boards[move][i][j];
13387                 if (emptycount > 0) {
13388                     if(emptycount<10) /* [HGM] can be >= 10 */
13389                         *p++ = '0' + emptycount;
13390                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13391                     emptycount = 0;
13392                 }
13393                 if(PieceToChar(piece) == '+') {
13394                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13395                     *p++ = '+';
13396                     piece = (ChessSquare)(DEMOTED piece);
13397                 } 
13398                 *p++ = PieceToChar(piece);
13399                 if(p[-1] == '~') {
13400                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13401                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13402                     *p++ = '~';
13403                 }
13404             }
13405         }
13406         if (emptycount > 0) {
13407             if(emptycount<10) /* [HGM] can be >= 10 */
13408                 *p++ = '0' + emptycount;
13409             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13410             emptycount = 0;
13411         }
13412         *p++ = '/';
13413     }
13414     *(p - 1) = ' ';
13415
13416     /* [HGM] print Crazyhouse or Shogi holdings */
13417     if( gameInfo.holdingsWidth ) {
13418         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13419         q = p;
13420         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13421             piece = boards[move][i][BOARD_WIDTH-1];
13422             if( piece != EmptySquare )
13423               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13424                   *p++ = PieceToChar(piece);
13425         }
13426         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13427             piece = boards[move][BOARD_HEIGHT-i-1][0];
13428             if( piece != EmptySquare )
13429               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13430                   *p++ = PieceToChar(piece);
13431         }
13432
13433         if( q == p ) *p++ = '-';
13434         *p++ = ']';
13435         *p++ = ' ';
13436     }
13437
13438     /* Active color */
13439     *p++ = whiteToPlay ? 'w' : 'b';
13440     *p++ = ' ';
13441
13442   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13443     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13444   } else {
13445   if(nrCastlingRights) {
13446      q = p;
13447      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13448        /* [HGM] write directly from rights */
13449            if(castlingRights[move][2] >= 0 &&
13450               castlingRights[move][0] >= 0   )
13451                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13452            if(castlingRights[move][2] >= 0 &&
13453               castlingRights[move][1] >= 0   )
13454                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13455            if(castlingRights[move][5] >= 0 &&
13456               castlingRights[move][3] >= 0   )
13457                 *p++ = castlingRights[move][3] + AAA;
13458            if(castlingRights[move][5] >= 0 &&
13459               castlingRights[move][4] >= 0   )
13460                 *p++ = castlingRights[move][4] + AAA;
13461      } else {
13462
13463         /* [HGM] write true castling rights */
13464         if( nrCastlingRights == 6 ) {
13465             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13466                castlingRights[move][2] >= 0  ) *p++ = 'K';
13467             if(castlingRights[move][1] == BOARD_LEFT &&
13468                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13469             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13470                castlingRights[move][5] >= 0  ) *p++ = 'k';
13471             if(castlingRights[move][4] == BOARD_LEFT &&
13472                castlingRights[move][5] >= 0  ) *p++ = 'q';
13473         }
13474      }
13475      if (q == p) *p++ = '-'; /* No castling rights */
13476      *p++ = ' ';
13477   }
13478
13479   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13480      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13481     /* En passant target square */
13482     if (move > backwardMostMove) {
13483         fromX = moveList[move - 1][0] - AAA;
13484         fromY = moveList[move - 1][1] - ONE;
13485         toX = moveList[move - 1][2] - AAA;
13486         toY = moveList[move - 1][3] - ONE;
13487         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13488             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13489             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13490             fromX == toX) {
13491             /* 2-square pawn move just happened */
13492             *p++ = toX + AAA;
13493             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13494         } else {
13495             *p++ = '-';
13496         }
13497     } else {
13498         *p++ = '-';
13499     }
13500     *p++ = ' ';
13501   }
13502   }
13503
13504     /* [HGM] find reversible plies */
13505     {   int i = 0, j=move;
13506
13507         if (appData.debugMode) { int k;
13508             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13509             for(k=backwardMostMove; k<=forwardMostMove; k++)
13510                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13511
13512         }
13513
13514         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13515         if( j == backwardMostMove ) i += initialRulePlies;
13516         sprintf(p, "%d ", i);
13517         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13518     }
13519     /* Fullmove number */
13520     sprintf(p, "%d", (move / 2) + 1);
13521     
13522     return StrSave(buf);
13523 }
13524
13525 Boolean
13526 ParseFEN(board, blackPlaysFirst, fen)
13527     Board board;
13528      int *blackPlaysFirst;
13529      char *fen;
13530 {
13531     int i, j;
13532     char *p;
13533     int emptycount;
13534     ChessSquare piece;
13535
13536     p = fen;
13537
13538     /* [HGM] by default clear Crazyhouse holdings, if present */
13539     if(gameInfo.holdingsWidth) {
13540        for(i=0; i<BOARD_HEIGHT; i++) {
13541            board[i][0]             = EmptySquare; /* black holdings */
13542            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13543            board[i][1]             = (ChessSquare) 0; /* black counts */
13544            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13545        }
13546     }
13547
13548     /* Piece placement data */
13549     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13550         j = 0;
13551         for (;;) {
13552             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13553                 if (*p == '/') p++;
13554                 emptycount = gameInfo.boardWidth - j;
13555                 while (emptycount--)
13556                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13557                 break;
13558 #if(BOARD_SIZE >= 10)
13559             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13560                 p++; emptycount=10;
13561                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13562                 while (emptycount--)
13563                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13564 #endif
13565             } else if (isdigit(*p)) {
13566                 emptycount = *p++ - '0';
13567                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13568                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13569                 while (emptycount--)
13570                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13571             } else if (*p == '+' || isalpha(*p)) {
13572                 if (j >= gameInfo.boardWidth) return FALSE;
13573                 if(*p=='+') {
13574                     piece = CharToPiece(*++p);
13575                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13576                     piece = (ChessSquare) (PROMOTED piece ); p++;
13577                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13578                 } else piece = CharToPiece(*p++);
13579
13580                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13581                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13582                     piece = (ChessSquare) (PROMOTED piece);
13583                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13584                     p++;
13585                 }
13586                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13587             } else {
13588                 return FALSE;
13589             }
13590         }
13591     }
13592     while (*p == '/' || *p == ' ') p++;
13593
13594     /* [HGM] look for Crazyhouse holdings here */
13595     while(*p==' ') p++;
13596     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13597         if(*p == '[') p++;
13598         if(*p == '-' ) *p++; /* empty holdings */ else {
13599             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13600             /* if we would allow FEN reading to set board size, we would   */
13601             /* have to add holdings and shift the board read so far here   */
13602             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13603                 *p++;
13604                 if((int) piece >= (int) BlackPawn ) {
13605                     i = (int)piece - (int)BlackPawn;
13606                     i = PieceToNumber((ChessSquare)i);
13607                     if( i >= gameInfo.holdingsSize ) return FALSE;
13608                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13609                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13610                 } else {
13611                     i = (int)piece - (int)WhitePawn;
13612                     i = PieceToNumber((ChessSquare)i);
13613                     if( i >= gameInfo.holdingsSize ) return FALSE;
13614                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13615                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13616                 }
13617             }
13618         }
13619         if(*p == ']') *p++;
13620     }
13621
13622     while(*p == ' ') p++;
13623
13624     /* Active color */
13625     switch (*p++) {
13626       case 'w':
13627         *blackPlaysFirst = FALSE;
13628         break;
13629       case 'b': 
13630         *blackPlaysFirst = TRUE;
13631         break;
13632       default:
13633         return FALSE;
13634     }
13635
13636     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13637     /* return the extra info in global variiables             */
13638
13639     /* set defaults in case FEN is incomplete */
13640     FENepStatus = EP_UNKNOWN;
13641     for(i=0; i<nrCastlingRights; i++ ) {
13642         FENcastlingRights[i] =
13643             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13644     }   /* assume possible unless obviously impossible */
13645     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13646     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13647     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13648     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13649     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13650     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13651     FENrulePlies = 0;
13652
13653     while(*p==' ') p++;
13654     if(nrCastlingRights) {
13655       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13656           /* castling indicator present, so default becomes no castlings */
13657           for(i=0; i<nrCastlingRights; i++ ) {
13658                  FENcastlingRights[i] = -1;
13659           }
13660       }
13661       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13662              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13663              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13664              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13665         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13666
13667         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13668             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13669             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13670         }
13671         switch(c) {
13672           case'K':
13673               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13674               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13675               FENcastlingRights[2] = whiteKingFile;
13676               break;
13677           case'Q':
13678               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13679               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13680               FENcastlingRights[2] = whiteKingFile;
13681               break;
13682           case'k':
13683               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13684               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13685               FENcastlingRights[5] = blackKingFile;
13686               break;
13687           case'q':
13688               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13689               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13690               FENcastlingRights[5] = blackKingFile;
13691           case '-':
13692               break;
13693           default: /* FRC castlings */
13694               if(c >= 'a') { /* black rights */
13695                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13696                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13697                   if(i == BOARD_RGHT) break;
13698                   FENcastlingRights[5] = i;
13699                   c -= AAA;
13700                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13701                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13702                   if(c > i)
13703                       FENcastlingRights[3] = c;
13704                   else
13705                       FENcastlingRights[4] = c;
13706               } else { /* white rights */
13707                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13708                     if(board[0][i] == WhiteKing) break;
13709                   if(i == BOARD_RGHT) break;
13710                   FENcastlingRights[2] = i;
13711                   c -= AAA - 'a' + 'A';
13712                   if(board[0][c] >= WhiteKing) break;
13713                   if(c > i)
13714                       FENcastlingRights[0] = c;
13715                   else
13716                       FENcastlingRights[1] = c;
13717               }
13718         }
13719       }
13720     if (appData.debugMode) {
13721         fprintf(debugFP, "FEN castling rights:");
13722         for(i=0; i<nrCastlingRights; i++)
13723         fprintf(debugFP, " %d", FENcastlingRights[i]);
13724         fprintf(debugFP, "\n");
13725     }
13726
13727       while(*p==' ') p++;
13728     }
13729
13730     /* read e.p. field in games that know e.p. capture */
13731     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13732        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13733       if(*p=='-') {
13734         p++; FENepStatus = EP_NONE;
13735       } else {
13736          char c = *p++ - AAA;
13737
13738          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13739          if(*p >= '0' && *p <='9') *p++;
13740          FENepStatus = c;
13741       }
13742     }
13743
13744
13745     if(sscanf(p, "%d", &i) == 1) {
13746         FENrulePlies = i; /* 50-move ply counter */
13747         /* (The move number is still ignored)    */
13748     }
13749
13750     return TRUE;
13751 }
13752       
13753 void
13754 EditPositionPasteFEN(char *fen)
13755 {
13756   if (fen != NULL) {
13757     Board initial_position;
13758
13759     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13760       DisplayError(_("Bad FEN position in clipboard"), 0);
13761       return ;
13762     } else {
13763       int savedBlackPlaysFirst = blackPlaysFirst;
13764       EditPositionEvent();
13765       blackPlaysFirst = savedBlackPlaysFirst;
13766       CopyBoard(boards[0], initial_position);
13767           /* [HGM] copy FEN attributes as well */
13768           {   int i;
13769               initialRulePlies = FENrulePlies;
13770               epStatus[0] = FENepStatus;
13771               for( i=0; i<nrCastlingRights; i++ )
13772                   castlingRights[0][i] = FENcastlingRights[i];
13773           }
13774       EditPositionDone();
13775       DisplayBothClocks();
13776       DrawPosition(FALSE, boards[currentMove]);
13777     }
13778   }
13779 }