new bitmap converter (including fill option) and new pixmaps
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187 void DisplayAnalysis P((void));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
242 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
246 int opponentKibitzes;
247 int lastSavedGame; /* [HGM] save: ID of game */
248 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
249 extern int chatCount;
250 int chattingPartner;
251
252 /* States for ics_getting_history */
253 #define H_FALSE 0
254 #define H_REQUESTED 1
255 #define H_GOT_REQ_HEADER 2
256 #define H_GOT_UNREQ_HEADER 3
257 #define H_GETTING_MOVES 4
258 #define H_GOT_UNWANTED_HEADER 5
259
260 /* whosays values for GameEnds */
261 #define GE_ICS 0
262 #define GE_ENGINE 1
263 #define GE_PLAYER 2
264 #define GE_FILE 3
265 #define GE_XBOARD 4
266 #define GE_ENGINE1 5
267 #define GE_ENGINE2 6
268
269 /* Maximum number of games in a cmail message */
270 #define CMAIL_MAX_GAMES 20
271
272 /* Different types of move when calling RegisterMove */
273 #define CMAIL_MOVE   0
274 #define CMAIL_RESIGN 1
275 #define CMAIL_DRAW   2
276 #define CMAIL_ACCEPT 3
277
278 /* Different types of result to remember for each game */
279 #define CMAIL_NOT_RESULT 0
280 #define CMAIL_OLD_RESULT 1
281 #define CMAIL_NEW_RESULT 2
282
283 /* Telnet protocol constants */
284 #define TN_WILL 0373
285 #define TN_WONT 0374
286 #define TN_DO   0375
287 #define TN_DONT 0376
288 #define TN_IAC  0377
289 #define TN_ECHO 0001
290 #define TN_SGA  0003
291 #define TN_PORT 23
292
293 /* [AS] */
294 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 {
296     assert( dst != NULL );
297     assert( src != NULL );
298     assert( count > 0 );
299
300     strncpy( dst, src, count );
301     dst[ count-1 ] = '\0';
302     return dst;
303 }
304
305 /* Some compiler can't cast u64 to double
306  * This function do the job for us:
307
308  * We use the highest bit for cast, this only
309  * works if the highest bit is not
310  * in use (This should not happen)
311  *
312  * We used this for all compiler
313  */
314 double
315 u64ToDouble(u64 value)
316 {
317   double r;
318   u64 tmp = value & u64Const(0x7fffffffffffffff);
319   r = (double)(s64)tmp;
320   if (value & u64Const(0x8000000000000000))
321        r +=  9.2233720368547758080e18; /* 2^63 */
322  return r;
323 }
324
325 /* Fake up flags for now, as we aren't keeping track of castling
326    availability yet. [HGM] Change of logic: the flag now only
327    indicates the type of castlings allowed by the rule of the game.
328    The actual rights themselves are maintained in the array
329    castlingRights, as part of the game history, and are not probed
330    by this function.
331  */
332 int
333 PosFlags(index)
334 {
335   int flags = F_ALL_CASTLE_OK;
336   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
337   switch (gameInfo.variant) {
338   case VariantSuicide:
339     flags &= ~F_ALL_CASTLE_OK;
340   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
341     flags |= F_IGNORE_CHECK;
342   case VariantLosers:
343     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
344     break;
345   case VariantAtomic:
346     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347     break;
348   case VariantKriegspiel:
349     flags |= F_KRIEGSPIEL_CAPTURE;
350     break;
351   case VariantCapaRandom: 
352   case VariantFischeRandom:
353     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
354   case VariantNoCastle:
355   case VariantShatranj:
356   case VariantCourier:
357     flags &= ~F_ALL_CASTLE_OK;
358     break;
359   default:
360     break;
361   }
362   return flags;
363 }
364
365 FILE *gameFileFP, *debugFP;
366
367 /* 
368     [AS] Note: sometimes, the sscanf() function is used to parse the input
369     into a fixed-size buffer. Because of this, we must be prepared to
370     receive strings as long as the size of the input buffer, which is currently
371     set to 4K for Windows and 8K for the rest.
372     So, we must either allocate sufficiently large buffers here, or
373     reduce the size of the input buffer in the input reading part.
374 */
375
376 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
377 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
378 char thinkOutput1[MSG_SIZ*10];
379
380 ChessProgramState first, second;
381
382 /* premove variables */
383 int premoveToX = 0;
384 int premoveToY = 0;
385 int premoveFromX = 0;
386 int premoveFromY = 0;
387 int premovePromoChar = 0;
388 int gotPremove = 0;
389 Boolean alarmSounded;
390 /* end premove variables */
391
392 char *ics_prefix = "$";
393 int ics_type = ICS_GENERIC;
394
395 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
396 int pauseExamForwardMostMove = 0;
397 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
398 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
399 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
400 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
401 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
402 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
403 int whiteFlag = FALSE, blackFlag = FALSE;
404 int userOfferedDraw = FALSE;
405 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
406 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
407 int cmailMoveType[CMAIL_MAX_GAMES];
408 long ics_clock_paused = 0;
409 ProcRef icsPR = NoProc, cmailPR = NoProc;
410 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
411 GameMode gameMode = BeginningOfGame;
412 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
413 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
414 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
415 int hiddenThinkOutputState = 0; /* [AS] */
416 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
417 int adjudicateLossPlies = 6;
418 char white_holding[64], black_holding[64];
419 TimeMark lastNodeCountTime;
420 long lastNodeCount=0;
421 int have_sent_ICS_logon = 0;
422 int movesPerSession;
423 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
424 long timeControl_2; /* [AS] Allow separate time controls */
425 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
426 long timeRemaining[2][MAX_MOVES];
427 int matchGame = 0;
428 TimeMark programStartTime;
429 char ics_handle[MSG_SIZ];
430 int have_set_title = 0;
431
432 /* animateTraining preserves the state of appData.animate
433  * when Training mode is activated. This allows the
434  * response to be animated when appData.animate == TRUE and
435  * appData.animateDragging == TRUE.
436  */
437 Boolean animateTraining;
438
439 GameInfo gameInfo;
440
441 AppData appData;
442
443 Board boards[MAX_MOVES];
444 /* [HGM] Following 7 needed for accurate legality tests: */
445 signed char  epStatus[MAX_MOVES];
446 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
447 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
448 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
449 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int   initialRulePlies, FENrulePlies;
451 char  FENepStatus;
452 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int loadFlag = 0; 
454 int shuffleOpenings;
455 int mute; // mute all sounds
456
457 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
458     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
459         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
460     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
461         BlackKing, BlackBishop, BlackKnight, BlackRook }
462 };
463
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
465     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
466         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
467     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
468         BlackKing, BlackKing, BlackKnight, BlackRook }
469 };
470
471 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
472     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
473         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
474     { BlackRook, BlackMan, BlackBishop, BlackQueen,
475         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
476 };
477
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
479     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
480         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
482         BlackKing, BlackBishop, BlackKnight, BlackRook }
483 };
484
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
486     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
487         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
488     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
489         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
490 };
491
492
493 #if (BOARD_SIZE>=10)
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
495     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
496         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
497     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
498         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
499 };
500
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
503         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
505         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 };
507
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
509     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
510         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
512         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
513 };
514
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
516     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
517         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
518     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
519         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
520 };
521
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
523     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
524         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
525     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
526         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
527 };
528
529 #ifdef GOTHIC
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
532         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
533     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
534         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
535 };
536 #else // !GOTHIC
537 #define GothicArray CapablancaArray
538 #endif // !GOTHIC
539
540 #ifdef FALCON
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
543         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
545         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
546 };
547 #else // !FALCON
548 #define FalconArray CapablancaArray
549 #endif // !FALCON
550
551 #else // !(BOARD_SIZE>=10)
552 #define XiangqiPosition FIDEArray
553 #define CapablancaArray FIDEArray
554 #define GothicArray FIDEArray
555 #define GreatArray FIDEArray
556 #endif // !(BOARD_SIZE>=10)
557
558 #if (BOARD_SIZE>=12)
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
561         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
563         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 };
565 #else // !(BOARD_SIZE>=12)
566 #define CourierArray CapablancaArray
567 #endif // !(BOARD_SIZE>=12)
568
569
570 Board initialPosition;
571
572
573 /* Convert str to a rating. Checks for special cases of "----",
574
575    "++++", etc. Also strips ()'s */
576 int
577 string_to_rating(str)
578   char *str;
579 {
580   while(*str && !isdigit(*str)) ++str;
581   if (!*str)
582     return 0;   /* One of the special "no rating" cases */
583   else
584     return atoi(str);
585 }
586
587 void
588 ClearProgramStats()
589 {
590     /* Init programStats */
591     programStats.movelist[0] = 0;
592     programStats.depth = 0;
593     programStats.nr_moves = 0;
594     programStats.moves_left = 0;
595     programStats.nodes = 0;
596     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
597     programStats.score = 0;
598     programStats.got_only_move = 0;
599     programStats.got_fail = 0;
600     programStats.line_is_book = 0;
601 }
602
603 void
604 InitBackEnd1()
605 {
606     int matched, min, sec;
607
608     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609
610     GetTimeMark(&programStartTime);
611     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
612
613     ClearProgramStats();
614     programStats.ok_to_send = 1;
615     programStats.seen_stat = 0;
616
617     /*
618      * Initialize game list
619      */
620     ListNew(&gameList);
621
622
623     /*
624      * Internet chess server status
625      */
626     if (appData.icsActive) {
627         appData.matchMode = FALSE;
628         appData.matchGames = 0;
629 #if ZIPPY       
630         appData.noChessProgram = !appData.zippyPlay;
631 #else
632         appData.zippyPlay = FALSE;
633         appData.zippyTalk = FALSE;
634         appData.noChessProgram = TRUE;
635 #endif
636         if (*appData.icsHelper != NULLCHAR) {
637             appData.useTelnet = TRUE;
638             appData.telnetProgram = appData.icsHelper;
639         }
640     } else {
641         appData.zippyTalk = appData.zippyPlay = FALSE;
642     }
643
644     /* [AS] Initialize pv info list [HGM] and game state */
645     {
646         int i, j;
647
648         for( i=0; i<MAX_MOVES; i++ ) {
649             pvInfoList[i].depth = -1;
650             epStatus[i]=EP_NONE;
651             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
652         }
653     }
654
655     /*
656      * Parse timeControl resource
657      */
658     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
659                           appData.movesPerSession)) {
660         char buf[MSG_SIZ];
661         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
662         DisplayFatalError(buf, 0, 2);
663     }
664
665     /*
666      * Parse searchTime resource
667      */
668     if (*appData.searchTime != NULLCHAR) {
669         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670         if (matched == 1) {
671             searchTime = min * 60;
672         } else if (matched == 2) {
673             searchTime = min * 60 + sec;
674         } else {
675             char buf[MSG_SIZ];
676             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
677             DisplayFatalError(buf, 0, 2);
678         }
679     }
680
681     /* [AS] Adjudication threshold */
682     adjudicateLossThreshold = appData.adjudicateLossThreshold;
683     
684     first.which = "first";
685     second.which = "second";
686     first.maybeThinking = second.maybeThinking = FALSE;
687     first.pr = second.pr = NoProc;
688     first.isr = second.isr = NULL;
689     first.sendTime = second.sendTime = 2;
690     first.sendDrawOffers = 1;
691     if (appData.firstPlaysBlack) {
692         first.twoMachinesColor = "black\n";
693         second.twoMachinesColor = "white\n";
694     } else {
695         first.twoMachinesColor = "white\n";
696         second.twoMachinesColor = "black\n";
697     }
698     first.program = appData.firstChessProgram;
699     second.program = appData.secondChessProgram;
700     first.host = appData.firstHost;
701     second.host = appData.secondHost;
702     first.dir = appData.firstDirectory;
703     second.dir = appData.secondDirectory;
704     first.other = &second;
705     second.other = &first;
706     first.initString = appData.initString;
707     second.initString = appData.secondInitString;
708     first.computerString = appData.firstComputerString;
709     second.computerString = appData.secondComputerString;
710     first.useSigint = second.useSigint = TRUE;
711     first.useSigterm = second.useSigterm = TRUE;
712     first.reuse = appData.reuseFirst;
713     second.reuse = appData.reuseSecond;
714     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
715     second.nps = appData.secondNPS;
716     first.useSetboard = second.useSetboard = FALSE;
717     first.useSAN = second.useSAN = FALSE;
718     first.usePing = second.usePing = FALSE;
719     first.lastPing = second.lastPing = 0;
720     first.lastPong = second.lastPong = 0;
721     first.usePlayother = second.usePlayother = FALSE;
722     first.useColors = second.useColors = TRUE;
723     first.useUsermove = second.useUsermove = FALSE;
724     first.sendICS = second.sendICS = FALSE;
725     first.sendName = second.sendName = appData.icsActive;
726     first.sdKludge = second.sdKludge = FALSE;
727     first.stKludge = second.stKludge = FALSE;
728     TidyProgramName(first.program, first.host, first.tidy);
729     TidyProgramName(second.program, second.host, second.tidy);
730     first.matchWins = second.matchWins = 0;
731     strcpy(first.variants, appData.variant);
732     strcpy(second.variants, appData.variant);
733     first.analysisSupport = second.analysisSupport = 2; /* detect */
734     first.analyzing = second.analyzing = FALSE;
735     first.initDone = second.initDone = FALSE;
736
737     /* New features added by Tord: */
738     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
739     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
740     /* End of new features added by Tord. */
741     first.fenOverride  = appData.fenOverride1;
742     second.fenOverride = appData.fenOverride2;
743
744     /* [HGM] time odds: set factor for each machine */
745     first.timeOdds  = appData.firstTimeOdds;
746     second.timeOdds = appData.secondTimeOdds;
747     { int norm = 1;
748         if(appData.timeOddsMode) {
749             norm = first.timeOdds;
750             if(norm > second.timeOdds) norm = second.timeOdds;
751         }
752         first.timeOdds /= norm;
753         second.timeOdds /= norm;
754     }
755
756     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
757     first.accumulateTC = appData.firstAccumulateTC;
758     second.accumulateTC = appData.secondAccumulateTC;
759     first.maxNrOfSessions = second.maxNrOfSessions = 1;
760
761     /* [HGM] debug */
762     first.debug = second.debug = FALSE;
763     first.supportsNPS = second.supportsNPS = UNKNOWN;
764
765     /* [HGM] options */
766     first.optionSettings  = appData.firstOptions;
767     second.optionSettings = appData.secondOptions;
768
769     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
770     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
771     first.isUCI = appData.firstIsUCI; /* [AS] */
772     second.isUCI = appData.secondIsUCI; /* [AS] */
773     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
774     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775
776     if (appData.firstProtocolVersion > PROTOVER ||
777         appData.firstProtocolVersion < 1) {
778       char buf[MSG_SIZ];
779       sprintf(buf, _("protocol version %d not supported"),
780               appData.firstProtocolVersion);
781       DisplayFatalError(buf, 0, 2);
782     } else {
783       first.protocolVersion = appData.firstProtocolVersion;
784     }
785
786     if (appData.secondProtocolVersion > PROTOVER ||
787         appData.secondProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.secondProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       second.protocolVersion = appData.secondProtocolVersion;
794     }
795
796     if (appData.icsActive) {
797         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
798     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
799         appData.clockMode = FALSE;
800         first.sendTime = second.sendTime = 0;
801     }
802     
803 #if ZIPPY
804     /* Override some settings from environment variables, for backward
805        compatibility.  Unfortunately it's not feasible to have the env
806        vars just set defaults, at least in xboard.  Ugh.
807     */
808     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
809       ZippyInit();
810     }
811 #endif
812     
813     if (appData.noChessProgram) {
814         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
815         sprintf(programVersion, "%s", PACKAGE_STRING);
816     } else {
817       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
818       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
819       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
820     }
821
822     if (!appData.icsActive) {
823       char buf[MSG_SIZ];
824       /* Check for variants that are supported only in ICS mode,
825          or not at all.  Some that are accepted here nevertheless
826          have bugs; see comments below.
827       */
828       VariantClass variant = StringToVariant(appData.variant);
829       switch (variant) {
830       case VariantBughouse:     /* need four players and two boards */
831       case VariantKriegspiel:   /* need to hide pieces and move details */
832       /* case VariantFischeRandom: (Fabien: moved below) */
833         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
834         DisplayFatalError(buf, 0, 2);
835         return;
836
837       case VariantUnknown:
838       case VariantLoadable:
839       case Variant29:
840       case Variant30:
841       case Variant31:
842       case Variant32:
843       case Variant33:
844       case Variant34:
845       case Variant35:
846       case Variant36:
847       default:
848         sprintf(buf, _("Unknown variant name %s"), appData.variant);
849         DisplayFatalError(buf, 0, 2);
850         return;
851
852       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
853       case VariantFairy:      /* [HGM] TestLegality definitely off! */
854       case VariantGothic:     /* [HGM] should work */
855       case VariantCapablanca: /* [HGM] should work */
856       case VariantCourier:    /* [HGM] initial forced moves not implemented */
857       case VariantShogi:      /* [HGM] drops not tested for legality */
858       case VariantKnightmate: /* [HGM] should work */
859       case VariantCylinder:   /* [HGM] untested */
860       case VariantFalcon:     /* [HGM] untested */
861       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
862                                  offboard interposition not understood */
863       case VariantNormal:     /* definitely works! */
864       case VariantWildCastle: /* pieces not automatically shuffled */
865       case VariantNoCastle:   /* pieces not automatically shuffled */
866       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
867       case VariantLosers:     /* should work except for win condition,
868                                  and doesn't know captures are mandatory */
869       case VariantSuicide:    /* should work except for win condition,
870                                  and doesn't know captures are mandatory */
871       case VariantGiveaway:   /* should work except for win condition,
872                                  and doesn't know captures are mandatory */
873       case VariantTwoKings:   /* should work */
874       case VariantAtomic:     /* should work except for win condition */
875       case Variant3Check:     /* should work except for win condition */
876       case VariantShatranj:   /* should work except for all win conditions */
877       case VariantBerolina:   /* might work if TestLegality is off */
878       case VariantCapaRandom: /* should work */
879       case VariantJanus:      /* should work */
880       case VariantSuper:      /* experimental */
881       case VariantGreat:      /* experimental, requires legality testing to be off */
882         break;
883       }
884     }
885
886     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
887     InitEngineUCI( installDir, &second );
888 }
889
890 int NextIntegerFromString( char ** str, long * value )
891 {
892     int result = -1;
893     char * s = *str;
894
895     while( *s == ' ' || *s == '\t' ) {
896         s++;
897     }
898
899     *value = 0;
900
901     if( *s >= '0' && *s <= '9' ) {
902         while( *s >= '0' && *s <= '9' ) {
903             *value = *value * 10 + (*s - '0');
904             s++;
905         }
906
907         result = 0;
908     }
909
910     *str = s;
911
912     return result;
913 }
914
915 int NextTimeControlFromString( char ** str, long * value )
916 {
917     long temp;
918     int result = NextIntegerFromString( str, &temp );
919
920     if( result == 0 ) {
921         *value = temp * 60; /* Minutes */
922         if( **str == ':' ) {
923             (*str)++;
924             result = NextIntegerFromString( str, &temp );
925             *value += temp; /* Seconds */
926         }
927     }
928
929     return result;
930 }
931
932 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
933 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
934     int result = -1; long temp, temp2;
935
936     if(**str != '+') return -1; // old params remain in force!
937     (*str)++;
938     if( NextTimeControlFromString( str, &temp ) ) return -1;
939
940     if(**str != '/') {
941         /* time only: incremental or sudden-death time control */
942         if(**str == '+') { /* increment follows; read it */
943             (*str)++;
944             if(result = NextIntegerFromString( str, &temp2)) return -1;
945             *inc = temp2 * 1000;
946         } else *inc = 0;
947         *moves = 0; *tc = temp * 1000; 
948         return 0;
949     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
950
951     (*str)++; /* classical time control */
952     result = NextTimeControlFromString( str, &temp2);
953     if(result == 0) {
954         *moves = temp/60;
955         *tc    = temp2 * 1000;
956         *inc   = 0;
957     }
958     return result;
959 }
960
961 int GetTimeQuota(int movenr)
962 {   /* [HGM] get time to add from the multi-session time-control string */
963     int moves=1; /* kludge to force reading of first session */
964     long time, increment;
965     char *s = fullTimeControlString;
966
967     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968     do {
969         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
970         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
971         if(movenr == -1) return time;    /* last move before new session     */
972         if(!moves) return increment;     /* current session is incremental   */
973         if(movenr >= 0) movenr -= moves; /* we already finished this session */
974     } while(movenr >= -1);               /* try again for next session       */
975
976     return 0; // no new time quota on this move
977 }
978
979 int
980 ParseTimeControl(tc, ti, mps)
981      char *tc;
982      int ti;
983      int mps;
984 {
985   long tc1;
986   long tc2;
987   char buf[MSG_SIZ];
988   
989   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
990   if(ti > 0) {
991     if(mps)
992       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
993     else sprintf(buf, "+%s+%d", tc, ti);
994   } else {
995     if(mps)
996              sprintf(buf, "+%d/%s", mps, tc);
997     else sprintf(buf, "+%s", tc);
998   }
999   fullTimeControlString = StrSave(buf);
1000   
1001   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1002     return FALSE;
1003   }
1004   
1005   if( *tc == '/' ) {
1006     /* Parse second time control */
1007     tc++;
1008     
1009     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1010       return FALSE;
1011     }
1012     
1013     if( tc2 == 0 ) {
1014       return FALSE;
1015     }
1016     
1017     timeControl_2 = tc2 * 1000;
1018   }
1019   else {
1020     timeControl_2 = 0;
1021   }
1022   
1023   if( tc1 == 0 ) {
1024     return FALSE;
1025   }
1026   
1027   timeControl = tc1 * 1000;
1028   
1029   if (ti >= 0) {
1030     timeIncrement = ti * 1000;  /* convert to ms */
1031     movesPerSession = 0;
1032   } else {
1033     timeIncrement = 0;
1034     movesPerSession = mps;
1035   }
1036   return TRUE;
1037 }
1038
1039 void
1040 InitBackEnd2()
1041 {
1042     if (appData.debugMode) {
1043         fprintf(debugFP, "%s\n", programVersion);
1044     }
1045
1046     if (appData.matchGames > 0) {
1047         appData.matchMode = TRUE;
1048     } else if (appData.matchMode) {
1049         appData.matchGames = 1;
1050     }
1051     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052         appData.matchGames = appData.sameColorGames;
1053     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056     }
1057     Reset(TRUE, FALSE);
1058     if (appData.noChessProgram || first.protocolVersion == 1) {
1059       InitBackEnd3();
1060     } else {
1061       /* kludge: allow timeout for initial "feature" commands */
1062       FreezeUI();
1063       DisplayMessage("", _("Starting chess program"));
1064       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065     }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate(); 
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),  
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112     
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile; 
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway, 
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '(' 
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr") 
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in 
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754     
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958
1959    
1960    if (appData.debugMode) {
1961      fprintf(debugFP, "Switch board from %s to %s\n",
1962              VariantName(gameInfo.variant), VariantName(newVariant));
1963      setbuf(debugFP, NULL);
1964    }
1965    shuffleOpenings = 0;       /* [HGM] shuffle */
1966    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1967    switch(newVariant) 
1968      {
1969      case VariantShogi:
1970        newWidth = 9;  newHeight = 9;
1971        gameInfo.holdingsSize = 7;
1972      case VariantBughouse:
1973      case VariantCrazyhouse:
1974        newHoldingsWidth = 2; break;
1975      case VariantGreat:
1976        newWidth = 10;
1977      case VariantSuper:
1978        newHoldingsWidth = 2;
1979        gameInfo.holdingsSize = 8;
1980        return;
1981      case VariantGothic:
1982      case VariantCapablanca:
1983      case VariantCapaRandom:
1984        newWidth = 10;
1985      default:
1986        newHoldingsWidth = gameInfo.holdingsSize = 0;
1987      };
1988    
1989    if(newWidth  != gameInfo.boardWidth  ||
1990       newHeight != gameInfo.boardHeight ||
1991       newHoldingsWidth != gameInfo.holdingsWidth ) {
1992      
1993      /* shift position to new playing area, if needed */
1994      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995        for(i=0; i<BOARD_HEIGHT; i++) 
1996          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998              board[i][j];
1999        for(i=0; i<newHeight; i++) {
2000          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002        }
2003      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004        for(i=0; i<BOARD_HEIGHT; i++)
2005          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007              board[i][j];
2008      }
2009      gameInfo.boardWidth  = newWidth;
2010      gameInfo.boardHeight = newHeight;
2011      gameInfo.holdingsWidth = newHoldingsWidth;
2012      gameInfo.variant = newVariant;
2013      InitDrawingSizes(-2, 0);
2014      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2016    
2017    DrawPosition(TRUE, boards[currentMove]);
2018 }
2019
2020 static int loggedOn = FALSE;
2021
2022 /*-- Game start info cache: --*/
2023 int gs_gamenum;
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051     
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     char str[500];
2060     int i, oldi;
2061     int buf_len;
2062     int next_out;
2063     int tkind;
2064     int backup;    /* [DM] For zippy color lines */
2065     char *p;
2066     char talker[MSG_SIZ]; // [HGM] chat
2067     int channel;
2068
2069     if (appData.debugMode) {
2070       if (!error) {
2071         fprintf(debugFP, "<ICS: ");
2072         show_bytes(debugFP, data, count);
2073         fprintf(debugFP, "\n");
2074       }
2075     }
2076
2077     if (appData.debugMode) { int f = forwardMostMove;
2078         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2079                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2080     }
2081     if (count > 0) {
2082         /* If last read ended with a partial line that we couldn't parse,
2083            prepend it to the new read and try again. */
2084         if (leftover_len > 0) {
2085             for (i=0; i<leftover_len; i++)
2086               buf[i] = buf[leftover_start + i];
2087         }
2088
2089         /* Copy in new characters, removing nulls and \r's */
2090         buf_len = leftover_len;
2091         for (i = 0; i < count; i++) {
2092             if (data[i] != NULLCHAR && data[i] != '\r')
2093               buf[buf_len++] = data[i];
2094             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2095                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2096                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2097                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2098                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2099             }
2100         }
2101
2102         buf[buf_len] = NULLCHAR;
2103         next_out = leftover_len;
2104         leftover_start = 0;
2105         
2106         i = 0;
2107         while (i < buf_len) {
2108             /* Deal with part of the TELNET option negotiation
2109                protocol.  We refuse to do anything beyond the
2110                defaults, except that we allow the WILL ECHO option,
2111                which ICS uses to turn off password echoing when we are
2112                directly connected to it.  We reject this option
2113                if localLineEditing mode is on (always on in xboard)
2114                and we are talking to port 23, which might be a real
2115                telnet server that will try to keep WILL ECHO on permanently.
2116              */
2117             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2118                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2119                 unsigned char option;
2120                 oldi = i;
2121                 switch ((unsigned char) buf[++i]) {
2122                   case TN_WILL:
2123                     if (appData.debugMode)
2124                       fprintf(debugFP, "\n<WILL ");
2125                     switch (option = (unsigned char) buf[++i]) {
2126                       case TN_ECHO:
2127                         if (appData.debugMode)
2128                           fprintf(debugFP, "ECHO ");
2129                         /* Reply only if this is a change, according
2130                            to the protocol rules. */
2131                         if (remoteEchoOption) break;
2132                         if (appData.localLineEditing &&
2133                             atoi(appData.icsPort) == TN_PORT) {
2134                             TelnetRequest(TN_DONT, TN_ECHO);
2135                         } else {
2136                             EchoOff();
2137                             TelnetRequest(TN_DO, TN_ECHO);
2138                             remoteEchoOption = TRUE;
2139                         }
2140                         break;
2141                       default:
2142                         if (appData.debugMode)
2143                           fprintf(debugFP, "%d ", option);
2144                         /* Whatever this is, we don't want it. */
2145                         TelnetRequest(TN_DONT, option);
2146                         break;
2147                     }
2148                     break;
2149                   case TN_WONT:
2150                     if (appData.debugMode)
2151                       fprintf(debugFP, "\n<WONT ");
2152                     switch (option = (unsigned char) buf[++i]) {
2153                       case TN_ECHO:
2154                         if (appData.debugMode)
2155                           fprintf(debugFP, "ECHO ");
2156                         /* Reply only if this is a change, according
2157                            to the protocol rules. */
2158                         if (!remoteEchoOption) break;
2159                         EchoOn();
2160                         TelnetRequest(TN_DONT, TN_ECHO);
2161                         remoteEchoOption = FALSE;
2162                         break;
2163                       default:
2164                         if (appData.debugMode)
2165                           fprintf(debugFP, "%d ", (unsigned char) option);
2166                         /* Whatever this is, it must already be turned
2167                            off, because we never agree to turn on
2168                            anything non-default, so according to the
2169                            protocol rules, we don't reply. */
2170                         break;
2171                     }
2172                     break;
2173                   case TN_DO:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<DO ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       default:
2178                         /* Whatever this is, we refuse to do it. */
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "%d ", option);
2181                         TelnetRequest(TN_WONT, option);
2182                         break;
2183                     }
2184                     break;
2185                   case TN_DONT:
2186                     if (appData.debugMode)
2187                       fprintf(debugFP, "\n<DONT ");
2188                     switch (option = (unsigned char) buf[++i]) {
2189                       default:
2190                         if (appData.debugMode)
2191                           fprintf(debugFP, "%d ", option);
2192                         /* Whatever this is, we are already not doing
2193                            it, because we never agree to do anything
2194                            non-default, so according to the protocol
2195                            rules, we don't reply. */
2196                         break;
2197                     }
2198                     break;
2199                   case TN_IAC:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<IAC ");
2202                     /* Doubled IAC; pass it through */
2203                     i--;
2204                     break;
2205                   default:
2206                     if (appData.debugMode)
2207                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2208                     /* Drop all other telnet commands on the floor */
2209                     break;
2210                 }
2211                 if (oldi > next_out)
2212                   SendToPlayer(&buf[next_out], oldi - next_out);
2213                 if (++i > next_out)
2214                   next_out = i;
2215                 continue;
2216             }
2217                 
2218             /* OK, this at least will *usually* work */
2219             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2220                 loggedOn = TRUE;
2221             }
2222             
2223             if (loggedOn && !intfSet) {
2224                 if (ics_type == ICS_ICC) {
2225                   sprintf(str,
2226                           "/set-quietly interface %s\n/set-quietly style 12\n",
2227                           programVersion);
2228           if (!appData.noJoin)
2229               strcat(str, "/set-quietly wrap 0\n");
2230                 } else if (ics_type == ICS_CHESSNET) {
2231                   sprintf(str, "/style 12\n");
2232                 } else {
2233                   strcpy(str, "alias $ @\n$set interface ");
2234                   strcat(str, programVersion);
2235                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2236 #ifdef WIN32
2237                   strcat(str, "$iset nohighlight 1\n");
2238 #endif
2239           if (!appData.noJoin)
2240               strcat(str, "$iset nowrap 1\n");
2241                   strcat(str, "$iset lock 1\n$style 12\n");
2242                 }
2243                 SendToICS(str);
2244                 NotifyFrontendLogin();
2245                 intfSet = TRUE;
2246             }
2247
2248             if (started == STARTED_COMMENT) {
2249                 /* Accumulate characters in comment */
2250                 parse[parse_pos++] = buf[i];
2251                 if (buf[i] == '\n') {
2252                     parse[parse_pos] = NULLCHAR;
2253                     if(chattingPartner>=0) {
2254                         char mess[MSG_SIZ];
2255                         sprintf(mess, "%s%s", talker, parse);
2256                         OutputChatMessage(chattingPartner, mess);
2257                         chattingPartner = -1;
2258                     } else
2259                     if(!suppressKibitz) // [HGM] kibitz
2260                         AppendComment(forwardMostMove, StripHighlight(parse));
2261                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2262                         int nrDigit = 0, nrAlph = 0, i;
2263                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2264                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2265                         parse[parse_pos] = NULLCHAR;
2266                         // try to be smart: if it does not look like search info, it should go to
2267                         // ICS interaction window after all, not to engine-output window.
2268                         for(i=0; i<parse_pos; i++) { // count letters and digits
2269                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2270                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2271                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2272                         }
2273                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2274                             int depth=0; float score;
2275                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2276                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2277                                 pvInfoList[forwardMostMove-1].depth = depth;
2278                                 pvInfoList[forwardMostMove-1].score = 100*score;
2279                             }
2280                             OutputKibitz(suppressKibitz, parse);
2281                         } else {
2282                             char tmp[MSG_SIZ];
2283                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2284                             SendToPlayer(tmp, strlen(tmp));
2285                         }
2286                     }
2287                     started = STARTED_NONE;
2288                 } else {
2289                     /* Don't match patterns against characters in chatter */
2290                     i++;
2291                     continue;
2292                 }
2293             }
2294             if (started == STARTED_CHATTER) {
2295                 if (buf[i] != '\n') {
2296                     /* Don't match patterns against characters in chatter */
2297                     i++;
2298                     continue;
2299                 }
2300                 started = STARTED_NONE;
2301             }
2302
2303             /* Kludge to deal with rcmd protocol */
2304             if (firstTime && looking_at(buf, &i, "\001*")) {
2305                 DisplayFatalError(&buf[1], 0, 1);
2306                 continue;
2307             } else {
2308                 firstTime = FALSE;
2309             }
2310
2311             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2312                 ics_type = ICS_ICC;
2313                 ics_prefix = "/";
2314                 if (appData.debugMode)
2315                   fprintf(debugFP, "ics_type %d\n", ics_type);
2316                 continue;
2317             }
2318             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2319                 ics_type = ICS_FICS;
2320                 ics_prefix = "$";
2321                 if (appData.debugMode)
2322                   fprintf(debugFP, "ics_type %d\n", ics_type);
2323                 continue;
2324             }
2325             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2326                 ics_type = ICS_CHESSNET;
2327                 ics_prefix = "/";
2328                 if (appData.debugMode)
2329                   fprintf(debugFP, "ics_type %d\n", ics_type);
2330                 continue;
2331             }
2332
2333             if (!loggedOn &&
2334                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2335                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2336                  looking_at(buf, &i, "will be \"*\""))) {
2337               strcpy(ics_handle, star_match[0]);
2338               continue;
2339             }
2340
2341             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2342               char buf[MSG_SIZ];
2343               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2344               DisplayIcsInteractionTitle(buf);
2345               have_set_title = TRUE;
2346             }
2347
2348             /* skip finger notes */
2349             if (started == STARTED_NONE &&
2350                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2351                  (buf[i] == '1' && buf[i+1] == '0')) &&
2352                 buf[i+2] == ':' && buf[i+3] == ' ') {
2353               started = STARTED_CHATTER;
2354               i += 3;
2355               continue;
2356             }
2357
2358             /* skip formula vars */
2359             if (started == STARTED_NONE &&
2360                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2361               started = STARTED_CHATTER;
2362               i += 3;
2363               continue;
2364             }
2365
2366             oldi = i;
2367             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2368             if (appData.autoKibitz && started == STARTED_NONE && 
2369                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2370                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2371                 if(looking_at(buf, &i, "* kibitzes: ") &&
2372                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2373                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2374                         suppressKibitz = TRUE;
2375                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2376                                 && (gameMode == IcsPlayingWhite)) ||
2377                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2378                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2379                             started = STARTED_CHATTER; // own kibitz we simply discard
2380                         else {
2381                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2382                             parse_pos = 0; parse[0] = NULLCHAR;
2383                             savingComment = TRUE;
2384                             suppressKibitz = gameMode != IcsObserving ? 2 :
2385                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2386                         } 
2387                         continue;
2388                 } else
2389                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2390                     started = STARTED_CHATTER;
2391                     suppressKibitz = TRUE;
2392                 }
2393             } // [HGM] kibitz: end of patch
2394
2395 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2396
2397             // [HGM] chat: intercept tells by users for which we have an open chat window
2398             channel = -1;
2399             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2400                                            looking_at(buf, &i, "* whispers:") ||
2401                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2402                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2403                 int p;
2404                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2405                 chattingPartner = -1;
2406
2407                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2408                 for(p=0; p<MAX_CHAT; p++) {
2409                     if(channel == atoi(chatPartner[p])) {
2410                     talker[0] = '['; strcat(talker, "]");
2411                     chattingPartner = p; break;
2412                     }
2413                 } else
2414                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2415                 for(p=0; p<MAX_CHAT; p++) {
2416                     if(!strcmp("WHISPER", chatPartner[p])) {
2417                         talker[0] = '['; strcat(talker, "]");
2418                         chattingPartner = p; break;
2419                     }
2420                 }
2421                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2422                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2423                     talker[0] = 0;
2424                     chattingPartner = p; break;
2425                 }
2426                 if(chattingPartner<0) i = oldi; else {
2427                     started = STARTED_COMMENT;
2428                     parse_pos = 0; parse[0] = NULLCHAR;
2429                     savingComment = TRUE;
2430                     suppressKibitz = TRUE;
2431                 }
2432             } // [HGM] chat: end of patch
2433
2434             if (appData.zippyTalk || appData.zippyPlay) {
2435                 /* [DM] Backup address for color zippy lines */
2436                 backup = i;
2437 #if ZIPPY
2438        #ifdef WIN32
2439                if (loggedOn == TRUE)
2440                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2441                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2442        #else
2443                 if (ZippyControl(buf, &i) ||
2444                     ZippyConverse(buf, &i) ||
2445                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2446                       loggedOn = TRUE;
2447                       if (!appData.colorize) continue;
2448                 }
2449        #endif
2450 #endif
2451             } // [DM] 'else { ' deleted
2452                 if (
2453                     /* Regular tells and says */
2454                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2455                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2456                     looking_at(buf, &i, "* says: ") ||
2457                     /* Don't color "message" or "messages" output */
2458                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2459                     looking_at(buf, &i, "*. * at *:*: ") ||
2460                     looking_at(buf, &i, "--* (*:*): ") ||
2461                     /* Message notifications (same color as tells) */
2462                     looking_at(buf, &i, "* has left a message ") ||
2463                     looking_at(buf, &i, "* just sent you a message:\n") ||
2464                     /* Whispers and kibitzes */
2465                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2466                     looking_at(buf, &i, "* kibitzes: ") ||
2467                     /* Channel tells */
2468                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2469
2470                   if (tkind == 1 && strchr(star_match[0], ':')) {
2471                       /* Avoid "tells you:" spoofs in channels */
2472                      tkind = 3;
2473                   }
2474                   if (star_match[0][0] == NULLCHAR ||
2475                       strchr(star_match[0], ' ') ||
2476                       (tkind == 3 && strchr(star_match[1], ' '))) {
2477                     /* Reject bogus matches */
2478                     i = oldi;
2479                   } else {
2480                     if (appData.colorize) {
2481                       if (oldi > next_out) {
2482                         SendToPlayer(&buf[next_out], oldi - next_out);
2483                         next_out = oldi;
2484                       }
2485                       switch (tkind) {
2486                       case 1:
2487                         Colorize(ColorTell, FALSE);
2488                         curColor = ColorTell;
2489                         break;
2490                       case 2:
2491                         Colorize(ColorKibitz, FALSE);
2492                         curColor = ColorKibitz;
2493                         break;
2494                       case 3:
2495                         p = strrchr(star_match[1], '(');
2496                         if (p == NULL) {
2497                           p = star_match[1];
2498                         } else {
2499                           p++;
2500                         }
2501                         if (atoi(p) == 1) {
2502                           Colorize(ColorChannel1, FALSE);
2503                           curColor = ColorChannel1;
2504                         } else {
2505                           Colorize(ColorChannel, FALSE);
2506                           curColor = ColorChannel;
2507                         }
2508                         break;
2509                       case 5:
2510                         curColor = ColorNormal;
2511                         break;
2512                       }
2513                     }
2514                     if (started == STARTED_NONE && appData.autoComment &&
2515                         (gameMode == IcsObserving ||
2516                          gameMode == IcsPlayingWhite ||
2517                          gameMode == IcsPlayingBlack)) {
2518                       parse_pos = i - oldi;
2519                       memcpy(parse, &buf[oldi], parse_pos);
2520                       parse[parse_pos] = NULLCHAR;
2521                       started = STARTED_COMMENT;
2522                       savingComment = TRUE;
2523                     } else {
2524                       started = STARTED_CHATTER;
2525                       savingComment = FALSE;
2526                     }
2527                     loggedOn = TRUE;
2528                     continue;
2529                   }
2530                 }
2531
2532                 if (looking_at(buf, &i, "* s-shouts: ") ||
2533                     looking_at(buf, &i, "* c-shouts: ")) {
2534                     if (appData.colorize) {
2535                         if (oldi > next_out) {
2536                             SendToPlayer(&buf[next_out], oldi - next_out);
2537                             next_out = oldi;
2538                         }
2539                         Colorize(ColorSShout, FALSE);
2540                         curColor = ColorSShout;
2541                     }
2542                     loggedOn = TRUE;
2543                     started = STARTED_CHATTER;
2544                     continue;
2545                 }
2546
2547                 if (looking_at(buf, &i, "--->")) {
2548                     loggedOn = TRUE;
2549                     continue;
2550                 }
2551
2552                 if (looking_at(buf, &i, "* shouts: ") ||
2553                     looking_at(buf, &i, "--> ")) {
2554                     if (appData.colorize) {
2555                         if (oldi > next_out) {
2556                             SendToPlayer(&buf[next_out], oldi - next_out);
2557                             next_out = oldi;
2558                         }
2559                         Colorize(ColorShout, FALSE);
2560                         curColor = ColorShout;
2561                     }
2562                     loggedOn = TRUE;
2563                     started = STARTED_CHATTER;
2564                     continue;
2565                 }
2566
2567                 if (looking_at( buf, &i, "Challenge:")) {
2568                     if (appData.colorize) {
2569                         if (oldi > next_out) {
2570                             SendToPlayer(&buf[next_out], oldi - next_out);
2571                             next_out = oldi;
2572                         }
2573                         Colorize(ColorChallenge, FALSE);
2574                         curColor = ColorChallenge;
2575                     }
2576                     loggedOn = TRUE;
2577                     continue;
2578                 }
2579
2580                 if (looking_at(buf, &i, "* offers you") ||
2581                     looking_at(buf, &i, "* offers to be") ||
2582                     looking_at(buf, &i, "* would like to") ||
2583                     looking_at(buf, &i, "* requests to") ||
2584                     looking_at(buf, &i, "Your opponent offers") ||
2585                     looking_at(buf, &i, "Your opponent requests")) {
2586
2587                     if (appData.colorize) {
2588                         if (oldi > next_out) {
2589                             SendToPlayer(&buf[next_out], oldi - next_out);
2590                             next_out = oldi;
2591                         }
2592                         Colorize(ColorRequest, FALSE);
2593                         curColor = ColorRequest;
2594                     }
2595                     continue;
2596                 }
2597
2598                 if (looking_at(buf, &i, "* (*) seeking")) {
2599                     if (appData.colorize) {
2600                         if (oldi > next_out) {
2601                             SendToPlayer(&buf[next_out], oldi - next_out);
2602                             next_out = oldi;
2603                         }
2604                         Colorize(ColorSeek, FALSE);
2605                         curColor = ColorSeek;
2606                     }
2607                     continue;
2608             }
2609
2610             if (looking_at(buf, &i, "\\   ")) {
2611                 if (prevColor != ColorNormal) {
2612                     if (oldi > next_out) {
2613                         SendToPlayer(&buf[next_out], oldi - next_out);
2614                         next_out = oldi;
2615                     }
2616                     Colorize(prevColor, TRUE);
2617                     curColor = prevColor;
2618                 }
2619                 if (savingComment) {
2620                     parse_pos = i - oldi;
2621                     memcpy(parse, &buf[oldi], parse_pos);
2622                     parse[parse_pos] = NULLCHAR;
2623                     started = STARTED_COMMENT;
2624                 } else {
2625                     started = STARTED_CHATTER;
2626                 }
2627                 continue;
2628             }
2629
2630             if (looking_at(buf, &i, "Black Strength :") ||
2631                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2632                 looking_at(buf, &i, "<10>") ||
2633                 looking_at(buf, &i, "#@#")) {
2634                 /* Wrong board style */
2635                 loggedOn = TRUE;
2636                 SendToICS(ics_prefix);
2637                 SendToICS("set style 12\n");
2638                 SendToICS(ics_prefix);
2639                 SendToICS("refresh\n");
2640                 continue;
2641             }
2642             
2643             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2644                 ICSInitScript();
2645                 have_sent_ICS_logon = 1;
2646                 continue;
2647             }
2648               
2649             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2650                 (looking_at(buf, &i, "\n<12> ") ||
2651                  looking_at(buf, &i, "<12> "))) {
2652                 loggedOn = TRUE;
2653                 if (oldi > next_out) {
2654                     SendToPlayer(&buf[next_out], oldi - next_out);
2655                 }
2656                 next_out = i;
2657                 started = STARTED_BOARD;
2658                 parse_pos = 0;
2659                 continue;
2660             }
2661
2662             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2663                 looking_at(buf, &i, "<b1> ")) {
2664                 if (oldi > next_out) {
2665                     SendToPlayer(&buf[next_out], oldi - next_out);
2666                 }
2667                 next_out = i;
2668                 started = STARTED_HOLDINGS;
2669                 parse_pos = 0;
2670                 continue;
2671             }
2672
2673             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2674                 loggedOn = TRUE;
2675                 /* Header for a move list -- first line */
2676
2677                 switch (ics_getting_history) {
2678                   case H_FALSE:
2679                     switch (gameMode) {
2680                       case IcsIdle:
2681                       case BeginningOfGame:
2682                         /* User typed "moves" or "oldmoves" while we
2683                            were idle.  Pretend we asked for these
2684                            moves and soak them up so user can step
2685                            through them and/or save them.
2686                            */
2687                         Reset(FALSE, TRUE);
2688                         gameMode = IcsObserving;
2689                         ModeHighlight();
2690                         ics_gamenum = -1;
2691                         ics_getting_history = H_GOT_UNREQ_HEADER;
2692                         break;
2693                       case EditGame: /*?*/
2694                       case EditPosition: /*?*/
2695                         /* Should above feature work in these modes too? */
2696                         /* For now it doesn't */
2697                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2698                         break;
2699                       default:
2700                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2701                         break;
2702                     }
2703                     break;
2704                   case H_REQUESTED:
2705                     /* Is this the right one? */
2706                     if (gameInfo.white && gameInfo.black &&
2707                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2708                         strcmp(gameInfo.black, star_match[2]) == 0) {
2709                         /* All is well */
2710                         ics_getting_history = H_GOT_REQ_HEADER;
2711                     }
2712                     break;
2713                   case H_GOT_REQ_HEADER:
2714                   case H_GOT_UNREQ_HEADER:
2715                   case H_GOT_UNWANTED_HEADER:
2716                   case H_GETTING_MOVES:
2717                     /* Should not happen */
2718                     DisplayError(_("Error gathering move list: two headers"), 0);
2719                     ics_getting_history = H_FALSE;
2720                     break;
2721                 }
2722
2723                 /* Save player ratings into gameInfo if needed */
2724                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2725                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2726                     (gameInfo.whiteRating == -1 ||
2727                      gameInfo.blackRating == -1)) {
2728
2729                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2730                     gameInfo.blackRating = string_to_rating(star_match[3]);
2731                     if (appData.debugMode)
2732                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2733                               gameInfo.whiteRating, gameInfo.blackRating);
2734                 }
2735                 continue;
2736             }
2737
2738             if (looking_at(buf, &i,
2739               "* * match, initial time: * minute*, increment: * second")) {
2740                 /* Header for a move list -- second line */
2741                 /* Initial board will follow if this is a wild game */
2742                 if (gameInfo.event != NULL) free(gameInfo.event);
2743                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2744                 gameInfo.event = StrSave(str);
2745                 /* [HGM] we switched variant. Translate boards if needed. */
2746                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2747                 continue;
2748             }
2749
2750             if (looking_at(buf, &i, "Move  ")) {
2751                 /* Beginning of a move list */
2752                 switch (ics_getting_history) {
2753                   case H_FALSE:
2754                     /* Normally should not happen */
2755                     /* Maybe user hit reset while we were parsing */
2756                     break;
2757                   case H_REQUESTED:
2758                     /* Happens if we are ignoring a move list that is not
2759                      * the one we just requested.  Common if the user
2760                      * tries to observe two games without turning off
2761                      * getMoveList */
2762                     break;
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: nested"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                   case H_GOT_REQ_HEADER:
2769                     ics_getting_history = H_GETTING_MOVES;
2770                     started = STARTED_MOVES;
2771                     parse_pos = 0;
2772                     if (oldi > next_out) {
2773                         SendToPlayer(&buf[next_out], oldi - next_out);
2774                     }
2775                     break;
2776                   case H_GOT_UNREQ_HEADER:
2777                     ics_getting_history = H_GETTING_MOVES;
2778                     started = STARTED_MOVES_NOHIDE;
2779                     parse_pos = 0;
2780                     break;
2781                   case H_GOT_UNWANTED_HEADER:
2782                     ics_getting_history = H_FALSE;
2783                     break;
2784                 }
2785                 continue;
2786             }                           
2787             
2788             if (looking_at(buf, &i, "% ") ||
2789                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2790                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2791                 savingComment = FALSE;
2792                 switch (started) {
2793                   case STARTED_MOVES:
2794                   case STARTED_MOVES_NOHIDE:
2795                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2796                     parse[parse_pos + i - oldi] = NULLCHAR;
2797                     ParseGameHistory(parse);
2798 #if ZIPPY
2799                     if (appData.zippyPlay && first.initDone) {
2800                         FeedMovesToProgram(&first, forwardMostMove);
2801                         if (gameMode == IcsPlayingWhite) {
2802                             if (WhiteOnMove(forwardMostMove)) {
2803                                 if (first.sendTime) {
2804                                   if (first.useColors) {
2805                                     SendToProgram("black\n", &first); 
2806                                   }
2807                                   SendTimeRemaining(&first, TRUE);
2808                                 }
2809                                 if (first.useColors) {
2810                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2811                                 }
2812                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2813                                 first.maybeThinking = TRUE;
2814                             } else {
2815                                 if (first.usePlayother) {
2816                                   if (first.sendTime) {
2817                                     SendTimeRemaining(&first, TRUE);
2818                                   }
2819                                   SendToProgram("playother\n", &first);
2820                                   firstMove = FALSE;
2821                                 } else {
2822                                   firstMove = TRUE;
2823                                 }
2824                             }
2825                         } else if (gameMode == IcsPlayingBlack) {
2826                             if (!WhiteOnMove(forwardMostMove)) {
2827                                 if (first.sendTime) {
2828                                   if (first.useColors) {
2829                                     SendToProgram("white\n", &first);
2830                                   }
2831                                   SendTimeRemaining(&first, FALSE);
2832                                 }
2833                                 if (first.useColors) {
2834                                   SendToProgram("black\n", &first);
2835                                 }
2836                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2837                                 first.maybeThinking = TRUE;
2838                             } else {
2839                                 if (first.usePlayother) {
2840                                   if (first.sendTime) {
2841                                     SendTimeRemaining(&first, FALSE);
2842                                   }
2843                                   SendToProgram("playother\n", &first);
2844                                   firstMove = FALSE;
2845                                 } else {
2846                                   firstMove = TRUE;
2847                                 }
2848                             }
2849                         }                       
2850                     }
2851 #endif
2852                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2853                         /* Moves came from oldmoves or moves command
2854                            while we weren't doing anything else.
2855                            */
2856                         currentMove = forwardMostMove;
2857                         ClearHighlights();/*!!could figure this out*/
2858                         flipView = appData.flipView;
2859                         DrawPosition(FALSE, boards[currentMove]);
2860                         DisplayBothClocks();
2861                         sprintf(str, "%s vs. %s",
2862                                 gameInfo.white, gameInfo.black);
2863                         DisplayTitle(str);
2864                         gameMode = IcsIdle;
2865                     } else {
2866                         /* Moves were history of an active game */
2867                         if (gameInfo.resultDetails != NULL) {
2868                             free(gameInfo.resultDetails);
2869                             gameInfo.resultDetails = NULL;
2870                         }
2871                     }
2872                     HistorySet(parseList, backwardMostMove,
2873                                forwardMostMove, currentMove-1);
2874                     DisplayMove(currentMove - 1);
2875                     if (started == STARTED_MOVES) next_out = i;
2876                     started = STARTED_NONE;
2877                     ics_getting_history = H_FALSE;
2878                     break;
2879
2880                   case STARTED_OBSERVE:
2881                     started = STARTED_NONE;
2882                     SendToICS(ics_prefix);
2883                     SendToICS("refresh\n");
2884                     break;
2885
2886                   default:
2887                     break;
2888                 }
2889                 if(bookHit) { // [HGM] book: simulate book reply
2890                     static char bookMove[MSG_SIZ]; // a bit generous?
2891
2892                     programStats.nodes = programStats.depth = programStats.time = 
2893                     programStats.score = programStats.got_only_move = 0;
2894                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2895
2896                     strcpy(bookMove, "move ");
2897                     strcat(bookMove, bookHit);
2898                     HandleMachineMove(bookMove, &first);
2899                 }
2900                 continue;
2901             }
2902             
2903             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2904                  started == STARTED_HOLDINGS ||
2905                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2906                 /* Accumulate characters in move list or board */
2907                 parse[parse_pos++] = buf[i];
2908             }
2909             
2910             /* Start of game messages.  Mostly we detect start of game
2911                when the first board image arrives.  On some versions
2912                of the ICS, though, we need to do a "refresh" after starting
2913                to observe in order to get the current board right away. */
2914             if (looking_at(buf, &i, "Adding game * to observation list")) {
2915                 started = STARTED_OBSERVE;
2916                 continue;
2917             }
2918
2919             /* Handle auto-observe */
2920             if (appData.autoObserve &&
2921                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2922                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2923                 char *player;
2924                 /* Choose the player that was highlighted, if any. */
2925                 if (star_match[0][0] == '\033' ||
2926                     star_match[1][0] != '\033') {
2927                     player = star_match[0];
2928                 } else {
2929                     player = star_match[2];
2930                 }
2931                 sprintf(str, "%sobserve %s\n",
2932                         ics_prefix, StripHighlightAndTitle(player));
2933                 SendToICS(str);
2934
2935                 /* Save ratings from notify string */
2936                 strcpy(player1Name, star_match[0]);
2937                 player1Rating = string_to_rating(star_match[1]);
2938                 strcpy(player2Name, star_match[2]);
2939                 player2Rating = string_to_rating(star_match[3]);
2940
2941                 if (appData.debugMode)
2942                   fprintf(debugFP, 
2943                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2944                           player1Name, player1Rating,
2945                           player2Name, player2Rating);
2946
2947                 continue;
2948             }
2949
2950             /* Deal with automatic examine mode after a game,
2951                and with IcsObserving -> IcsExamining transition */
2952             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2953                 looking_at(buf, &i, "has made you an examiner of game *")) {
2954
2955                 int gamenum = atoi(star_match[0]);
2956                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2957                     gamenum == ics_gamenum) {
2958                     /* We were already playing or observing this game;
2959                        no need to refetch history */
2960                     gameMode = IcsExamining;
2961                     if (pausing) {
2962                         pauseExamForwardMostMove = forwardMostMove;
2963                     } else if (currentMove < forwardMostMove) {
2964                         ForwardInner(forwardMostMove);
2965                     }
2966                 } else {
2967                     /* I don't think this case really can happen */
2968                     SendToICS(ics_prefix);
2969                     SendToICS("refresh\n");
2970                 }
2971                 continue;
2972             }    
2973             
2974             /* Error messages */
2975 //          if (ics_user_moved) {
2976             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2977                 if (looking_at(buf, &i, "Illegal move") ||
2978                     looking_at(buf, &i, "Not a legal move") ||
2979                     looking_at(buf, &i, "Your king is in check") ||
2980                     looking_at(buf, &i, "It isn't your turn") ||
2981                     looking_at(buf, &i, "It is not your move")) {
2982                     /* Illegal move */
2983                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2984                         currentMove = --forwardMostMove;
2985                         DisplayMove(currentMove - 1); /* before DMError */
2986                         DrawPosition(FALSE, boards[currentMove]);
2987                         SwitchClocks();
2988                         DisplayBothClocks();
2989                     }
2990                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2991                     ics_user_moved = 0;
2992                     continue;
2993                 }
2994             }
2995
2996             if (looking_at(buf, &i, "still have time") ||
2997                 looking_at(buf, &i, "not out of time") ||
2998                 looking_at(buf, &i, "either player is out of time") ||
2999                 looking_at(buf, &i, "has timeseal; checking")) {
3000                 /* We must have called his flag a little too soon */
3001                 whiteFlag = blackFlag = FALSE;
3002                 continue;
3003             }
3004
3005             if (looking_at(buf, &i, "added * seconds to") ||
3006                 looking_at(buf, &i, "seconds were added to")) {
3007                 /* Update the clocks */
3008                 SendToICS(ics_prefix);
3009                 SendToICS("refresh\n");
3010                 continue;
3011             }
3012
3013             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3014                 ics_clock_paused = TRUE;
3015                 StopClocks();
3016                 continue;
3017             }
3018
3019             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3020                 ics_clock_paused = FALSE;
3021                 StartClocks();
3022                 continue;
3023             }
3024
3025             /* Grab player ratings from the Creating: message.
3026                Note we have to check for the special case when
3027                the ICS inserts things like [white] or [black]. */
3028             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3029                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3030                 /* star_matches:
3031                    0    player 1 name (not necessarily white)
3032                    1    player 1 rating
3033                    2    empty, white, or black (IGNORED)
3034                    3    player 2 name (not necessarily black)
3035                    4    player 2 rating
3036                    
3037                    The names/ratings are sorted out when the game
3038                    actually starts (below).
3039                 */
3040                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3041                 player1Rating = string_to_rating(star_match[1]);
3042                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3043                 player2Rating = string_to_rating(star_match[4]);
3044
3045                 if (appData.debugMode)
3046                   fprintf(debugFP, 
3047                           "Ratings from 'Creating:' %s %d, %s %d\n",
3048                           player1Name, player1Rating,
3049                           player2Name, player2Rating);
3050
3051                 continue;
3052             }
3053             
3054             /* Improved generic start/end-of-game messages */
3055             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3056                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3057                 /* If tkind == 0: */
3058                 /* star_match[0] is the game number */
3059                 /*           [1] is the white player's name */
3060                 /*           [2] is the black player's name */
3061                 /* For end-of-game: */
3062                 /*           [3] is the reason for the game end */
3063                 /*           [4] is a PGN end game-token, preceded by " " */
3064                 /* For start-of-game: */
3065                 /*           [3] begins with "Creating" or "Continuing" */
3066                 /*           [4] is " *" or empty (don't care). */
3067                 int gamenum = atoi(star_match[0]);
3068                 char *whitename, *blackname, *why, *endtoken;
3069                 ChessMove endtype = (ChessMove) 0;
3070
3071                 if (tkind == 0) {
3072                   whitename = star_match[1];
3073                   blackname = star_match[2];
3074                   why = star_match[3];
3075                   endtoken = star_match[4];
3076                 } else {
3077                   whitename = star_match[1];
3078                   blackname = star_match[3];
3079                   why = star_match[5];
3080                   endtoken = star_match[6];
3081                 }
3082
3083                 /* Game start messages */
3084                 if (strncmp(why, "Creating ", 9) == 0 ||
3085                     strncmp(why, "Continuing ", 11) == 0) {
3086                     gs_gamenum = gamenum;
3087                     strcpy(gs_kind, strchr(why, ' ') + 1);
3088 #if ZIPPY
3089                     if (appData.zippyPlay) {
3090                         ZippyGameStart(whitename, blackname);
3091                     }
3092 #endif /*ZIPPY*/
3093                     continue;
3094                 }
3095
3096                 /* Game end messages */
3097                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3098                     ics_gamenum != gamenum) {
3099                     continue;
3100                 }
3101                 while (endtoken[0] == ' ') endtoken++;
3102                 switch (endtoken[0]) {
3103                   case '*':
3104                   default:
3105                     endtype = GameUnfinished;
3106                     break;
3107                   case '0':
3108                     endtype = BlackWins;
3109                     break;
3110                   case '1':
3111                     if (endtoken[1] == '/')
3112                       endtype = GameIsDrawn;
3113                     else
3114                       endtype = WhiteWins;
3115                     break;
3116                 }
3117                 GameEnds(endtype, why, GE_ICS);
3118 #if ZIPPY
3119                 if (appData.zippyPlay && first.initDone) {
3120                     ZippyGameEnd(endtype, why);
3121                     if (first.pr == NULL) {
3122                       /* Start the next process early so that we'll
3123                          be ready for the next challenge */
3124                       StartChessProgram(&first);
3125                     }
3126                     /* Send "new" early, in case this command takes
3127                        a long time to finish, so that we'll be ready
3128                        for the next challenge. */
3129                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3130                     Reset(TRUE, TRUE);
3131                 }
3132 #endif /*ZIPPY*/
3133                 continue;
3134             }
3135
3136             if (looking_at(buf, &i, "Removing game * from observation") ||
3137                 looking_at(buf, &i, "no longer observing game *") ||
3138                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3139                 if (gameMode == IcsObserving &&
3140                     atoi(star_match[0]) == ics_gamenum)
3141                   {
3142                       /* icsEngineAnalyze */
3143                       if (appData.icsEngineAnalyze) {
3144                             ExitAnalyzeMode();
3145                             ModeHighlight();
3146                       }
3147                       StopClocks();
3148                       gameMode = IcsIdle;
3149                       ics_gamenum = -1;
3150                       ics_user_moved = FALSE;
3151                   }
3152                 continue;
3153             }
3154
3155             if (looking_at(buf, &i, "no longer examining game *")) {
3156                 if (gameMode == IcsExamining &&
3157                     atoi(star_match[0]) == ics_gamenum)
3158                   {
3159                       gameMode = IcsIdle;
3160                       ics_gamenum = -1;
3161                       ics_user_moved = FALSE;
3162                   }
3163                 continue;
3164             }
3165
3166             /* Advance leftover_start past any newlines we find,
3167                so only partial lines can get reparsed */
3168             if (looking_at(buf, &i, "\n")) {
3169                 prevColor = curColor;
3170                 if (curColor != ColorNormal) {
3171                     if (oldi > next_out) {
3172                         SendToPlayer(&buf[next_out], oldi - next_out);
3173                         next_out = oldi;
3174                     }
3175                     Colorize(ColorNormal, FALSE);
3176                     curColor = ColorNormal;
3177                 }
3178                 if (started == STARTED_BOARD) {
3179                     started = STARTED_NONE;
3180                     parse[parse_pos] = NULLCHAR;
3181                     ParseBoard12(parse);
3182                     ics_user_moved = 0;
3183
3184                     /* Send premove here */
3185                     if (appData.premove) {
3186                       char str[MSG_SIZ];
3187                       if (currentMove == 0 &&
3188                           gameMode == IcsPlayingWhite &&
3189                           appData.premoveWhite) {
3190                         sprintf(str, "%s%s\n", ics_prefix,
3191                                 appData.premoveWhiteText);
3192                         if (appData.debugMode)
3193                           fprintf(debugFP, "Sending premove:\n");
3194                         SendToICS(str);
3195                       } else if (currentMove == 1 &&
3196                                  gameMode == IcsPlayingBlack &&
3197                                  appData.premoveBlack) {
3198                         sprintf(str, "%s%s\n", ics_prefix,
3199                                 appData.premoveBlackText);
3200                         if (appData.debugMode)
3201                           fprintf(debugFP, "Sending premove:\n");
3202                         SendToICS(str);
3203                       } else if (gotPremove) {
3204                         gotPremove = 0;
3205                         ClearPremoveHighlights();
3206                         if (appData.debugMode)
3207                           fprintf(debugFP, "Sending premove:\n");
3208                           UserMoveEvent(premoveFromX, premoveFromY, 
3209                                         premoveToX, premoveToY, 
3210                                         premovePromoChar);
3211                       }
3212                     }
3213
3214                     /* Usually suppress following prompt */
3215                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3216                         if (looking_at(buf, &i, "*% ")) {
3217                             savingComment = FALSE;
3218                         }
3219                     }
3220                     next_out = i;
3221                 } else if (started == STARTED_HOLDINGS) {
3222                     int gamenum;
3223                     char new_piece[MSG_SIZ];
3224                     started = STARTED_NONE;
3225                     parse[parse_pos] = NULLCHAR;
3226                     if (appData.debugMode)
3227                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3228                                                         parse, currentMove);
3229                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3230                         gamenum == ics_gamenum) {
3231                         if (gameInfo.variant == VariantNormal) {
3232                           /* [HGM] We seem to switch variant during a game!
3233                            * Presumably no holdings were displayed, so we have
3234                            * to move the position two files to the right to
3235                            * create room for them!
3236                            */
3237                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3238                           /* Get a move list just to see the header, which
3239                              will tell us whether this is really bug or zh */
3240                           if (ics_getting_history == H_FALSE) {
3241                             ics_getting_history = H_REQUESTED;
3242                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3243                             SendToICS(str);
3244                           }
3245                         }
3246                         new_piece[0] = NULLCHAR;
3247                         sscanf(parse, "game %d white [%s black [%s <- %s",
3248                                &gamenum, white_holding, black_holding,
3249                                new_piece);
3250                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3251                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3252                         /* [HGM] copy holdings to board holdings area */
3253                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3254                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3255 #if ZIPPY
3256                         if (appData.zippyPlay && first.initDone) {
3257                             ZippyHoldings(white_holding, black_holding,
3258                                           new_piece);
3259                         }
3260 #endif /*ZIPPY*/
3261                         if (tinyLayout || smallLayout) {
3262                             char wh[16], bh[16];
3263                             PackHolding(wh, white_holding);
3264                             PackHolding(bh, black_holding);
3265                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3266                                     gameInfo.white, gameInfo.black);
3267                         } else {
3268                             sprintf(str, "%s [%s] vs. %s [%s]",
3269                                     gameInfo.white, white_holding,
3270                                     gameInfo.black, black_holding);
3271                         }
3272
3273                         DrawPosition(FALSE, boards[currentMove]);
3274                         DisplayTitle(str);
3275                     }
3276                     /* Suppress following prompt */
3277                     if (looking_at(buf, &i, "*% ")) {
3278                         savingComment = FALSE;
3279                     }
3280                     next_out = i;
3281                 }
3282                 continue;
3283             }
3284
3285             i++;                /* skip unparsed character and loop back */
3286         }
3287         
3288         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3289             started != STARTED_HOLDINGS && i > next_out) {
3290             SendToPlayer(&buf[next_out], i - next_out);
3291             next_out = i;
3292         }
3293         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3294         
3295         leftover_len = buf_len - leftover_start;
3296         /* if buffer ends with something we couldn't parse,
3297            reparse it after appending the next read */
3298         
3299     } else if (count == 0) {
3300         RemoveInputSource(isr);
3301         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3302     } else {
3303         DisplayFatalError(_("Error reading from ICS"), error, 1);
3304     }
3305 }
3306
3307
3308 /* Board style 12 looks like this:
3309    
3310    <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
3311    
3312  * The "<12> " is stripped before it gets to this routine.  The two
3313  * trailing 0's (flip state and clock ticking) are later addition, and
3314  * some chess servers may not have them, or may have only the first.
3315  * Additional trailing fields may be added in the future.  
3316  */
3317
3318 #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"
3319
3320 #define RELATION_OBSERVING_PLAYED    0
3321 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3322 #define RELATION_PLAYING_MYMOVE      1
3323 #define RELATION_PLAYING_NOTMYMOVE  -1
3324 #define RELATION_EXAMINING           2
3325 #define RELATION_ISOLATED_BOARD     -3
3326 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3327
3328 void
3329 ParseBoard12(string)
3330      char *string;
3331
3332     GameMode newGameMode;
3333     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3334     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3335     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3336     char to_play, board_chars[200];
3337     char move_str[500], str[500], elapsed_time[500];
3338     char black[32], white[32];
3339     Board board;
3340     int prevMove = currentMove;
3341     int ticking = 2;
3342     ChessMove moveType;
3343     int fromX, fromY, toX, toY;
3344     char promoChar;
3345     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3346     char *bookHit = NULL; // [HGM] book
3347
3348     fromX = fromY = toX = toY = -1;
3349     
3350     newGame = FALSE;
3351
3352     if (appData.debugMode)
3353       fprintf(debugFP, _("Parsing board: %s\n"), string);
3354
3355     move_str[0] = NULLCHAR;
3356     elapsed_time[0] = NULLCHAR;
3357     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3358         int  i = 0, j;
3359         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3360             if(string[i] == ' ') { ranks++; files = 0; }
3361             else files++;
3362             i++;
3363         }
3364         for(j = 0; j <i; j++) board_chars[j] = string[j];
3365         board_chars[i] = '\0';
3366         string += i + 1;
3367     }
3368     n = sscanf(string, PATTERN, &to_play, &double_push,
3369                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3370                &gamenum, white, black, &relation, &basetime, &increment,
3371                &white_stren, &black_stren, &white_time, &black_time,
3372                &moveNum, str, elapsed_time, move_str, &ics_flip,
3373                &ticking);
3374
3375     if (n < 21) {
3376         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3377         DisplayError(str, 0);
3378         return;
3379     }
3380
3381     /* Convert the move number to internal form */
3382     moveNum = (moveNum - 1) * 2;
3383     if (to_play == 'B') moveNum++;
3384     if (moveNum >= MAX_MOVES) {
3385       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3386                         0, 1);
3387       return;
3388     }
3389     
3390     switch (relation) {
3391       case RELATION_OBSERVING_PLAYED:
3392       case RELATION_OBSERVING_STATIC:
3393         if (gamenum == -1) {
3394             /* Old ICC buglet */
3395             relation = RELATION_OBSERVING_STATIC;
3396         }
3397         newGameMode = IcsObserving;
3398         break;
3399       case RELATION_PLAYING_MYMOVE:
3400       case RELATION_PLAYING_NOTMYMOVE:
3401         newGameMode =
3402           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3403             IcsPlayingWhite : IcsPlayingBlack;
3404         break;
3405       case RELATION_EXAMINING:
3406         newGameMode = IcsExamining;
3407         break;
3408       case RELATION_ISOLATED_BOARD:
3409       default:
3410         /* Just display this board.  If user was doing something else,
3411            we will forget about it until the next board comes. */ 
3412         newGameMode = IcsIdle;
3413         break;
3414       case RELATION_STARTING_POSITION:
3415         newGameMode = gameMode;
3416         break;
3417     }
3418     
3419     /* Modify behavior for initial board display on move listing
3420        of wild games.
3421        */
3422     switch (ics_getting_history) {
3423       case H_FALSE:
3424       case H_REQUESTED:
3425         break;
3426       case H_GOT_REQ_HEADER:
3427       case H_GOT_UNREQ_HEADER:
3428         /* This is the initial position of the current game */
3429         gamenum = ics_gamenum;
3430         moveNum = 0;            /* old ICS bug workaround */
3431         if (to_play == 'B') {
3432           startedFromSetupPosition = TRUE;
3433           blackPlaysFirst = TRUE;
3434           moveNum = 1;
3435           if (forwardMostMove == 0) forwardMostMove = 1;
3436           if (backwardMostMove == 0) backwardMostMove = 1;
3437           if (currentMove == 0) currentMove = 1;
3438         }
3439         newGameMode = gameMode;
3440         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3441         break;
3442       case H_GOT_UNWANTED_HEADER:
3443         /* This is an initial board that we don't want */
3444         return;
3445       case H_GETTING_MOVES:
3446         /* Should not happen */
3447         DisplayError(_("Error gathering move list: extra board"), 0);
3448         ics_getting_history = H_FALSE;
3449         return;
3450     }
3451     
3452     /* Take action if this is the first board of a new game, or of a
3453        different game than is currently being displayed.  */
3454     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3455         relation == RELATION_ISOLATED_BOARD) {
3456         
3457         /* Forget the old game and get the history (if any) of the new one */
3458         if (gameMode != BeginningOfGame) {
3459           Reset(FALSE, TRUE);
3460         }
3461         newGame = TRUE;
3462         if (appData.autoRaiseBoard) BoardToTop();
3463         prevMove = -3;
3464         if (gamenum == -1) {
3465             newGameMode = IcsIdle;
3466         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3467                    appData.getMoveList) {
3468             /* Need to get game history */
3469             ics_getting_history = H_REQUESTED;
3470             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3471             SendToICS(str);
3472         }
3473         
3474         /* Initially flip the board to have black on the bottom if playing
3475            black or if the ICS flip flag is set, but let the user change
3476            it with the Flip View button. */
3477         flipView = appData.autoFlipView ? 
3478           (newGameMode == IcsPlayingBlack) || ics_flip :
3479           appData.flipView;
3480         
3481         /* Done with values from previous mode; copy in new ones */
3482         gameMode = newGameMode;
3483         ModeHighlight();
3484         ics_gamenum = gamenum;
3485         if (gamenum == gs_gamenum) {
3486             int klen = strlen(gs_kind);
3487             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3488             sprintf(str, "ICS %s", gs_kind);
3489             gameInfo.event = StrSave(str);
3490         } else {
3491             gameInfo.event = StrSave("ICS game");
3492         }
3493         gameInfo.site = StrSave(appData.icsHost);
3494         gameInfo.date = PGNDate();
3495         gameInfo.round = StrSave("-");
3496         gameInfo.white = StrSave(white);
3497         gameInfo.black = StrSave(black);
3498         timeControl = basetime * 60 * 1000;
3499         timeControl_2 = 0;
3500         timeIncrement = increment * 1000;
3501         movesPerSession = 0;
3502         gameInfo.timeControl = TimeControlTagValue();
3503         VariantSwitch(board, StringToVariant(gameInfo.event) );
3504   if (appData.debugMode) {
3505     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3506     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3507     setbuf(debugFP, NULL);
3508   }
3509
3510         gameInfo.outOfBook = NULL;
3511         
3512         /* Do we have the ratings? */
3513         if (strcmp(player1Name, white) == 0 &&
3514             strcmp(player2Name, black) == 0) {
3515             if (appData.debugMode)
3516               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3517                       player1Rating, player2Rating);
3518             gameInfo.whiteRating = player1Rating;
3519             gameInfo.blackRating = player2Rating;
3520         } else if (strcmp(player2Name, white) == 0 &&
3521                    strcmp(player1Name, black) == 0) {
3522             if (appData.debugMode)
3523               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3524                       player2Rating, player1Rating);
3525             gameInfo.whiteRating = player2Rating;
3526             gameInfo.blackRating = player1Rating;
3527         }
3528         player1Name[0] = player2Name[0] = NULLCHAR;
3529
3530         /* Silence shouts if requested */
3531         if (appData.quietPlay &&
3532             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3533             SendToICS(ics_prefix);
3534             SendToICS("set shout 0\n");
3535         }
3536     }
3537     
3538     /* Deal with midgame name changes */
3539     if (!newGame) {
3540         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3541             if (gameInfo.white) free(gameInfo.white);
3542             gameInfo.white = StrSave(white);
3543         }
3544         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3545             if (gameInfo.black) free(gameInfo.black);
3546             gameInfo.black = StrSave(black);
3547         }
3548     }
3549     
3550     /* Throw away game result if anything actually changes in examine mode */
3551     if (gameMode == IcsExamining && !newGame) {
3552         gameInfo.result = GameUnfinished;
3553         if (gameInfo.resultDetails != NULL) {
3554             free(gameInfo.resultDetails);
3555             gameInfo.resultDetails = NULL;
3556         }
3557     }
3558     
3559     /* In pausing && IcsExamining mode, we ignore boards coming
3560        in if they are in a different variation than we are. */
3561     if (pauseExamInvalid) return;
3562     if (pausing && gameMode == IcsExamining) {
3563         if (moveNum <= pauseExamForwardMostMove) {
3564             pauseExamInvalid = TRUE;
3565             forwardMostMove = pauseExamForwardMostMove;
3566             return;
3567         }
3568     }
3569     
3570   if (appData.debugMode) {
3571     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3572   }
3573     /* Parse the board */
3574     for (k = 0; k < ranks; k++) {
3575       for (j = 0; j < files; j++)
3576         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3577       if(gameInfo.holdingsWidth > 1) {
3578            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3579            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3580       }
3581     }
3582     CopyBoard(boards[moveNum], board);
3583     if (moveNum == 0) {
3584         startedFromSetupPosition =
3585           !CompareBoards(board, initialPosition);
3586         if(startedFromSetupPosition)
3587             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3588     }
3589
3590     /* [HGM] Set castling rights. Take the outermost Rooks,
3591        to make it also work for FRC opening positions. Note that board12
3592        is really defective for later FRC positions, as it has no way to
3593        indicate which Rook can castle if they are on the same side of King.
3594        For the initial position we grant rights to the outermost Rooks,
3595        and remember thos rights, and we then copy them on positions
3596        later in an FRC game. This means WB might not recognize castlings with
3597        Rooks that have moved back to their original position as illegal,
3598        but in ICS mode that is not its job anyway.
3599     */
3600     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3601     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3602
3603         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3604             if(board[0][i] == WhiteRook) j = i;
3605         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3606         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3607             if(board[0][i] == WhiteRook) j = i;
3608         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3609         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3610             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3611         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3612         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3613             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3614         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3615
3616         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3617         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3618             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3619         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3620             if(board[BOARD_HEIGHT-1][k] == bKing)
3621                 initialRights[5] = castlingRights[moveNum][5] = k;
3622     } else { int r;
3623         r = castlingRights[moveNum][0] = initialRights[0];
3624         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3625         r = castlingRights[moveNum][1] = initialRights[1];
3626         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3627         r = castlingRights[moveNum][3] = initialRights[3];
3628         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3629         r = castlingRights[moveNum][4] = initialRights[4];
3630         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3631         /* wildcastle kludge: always assume King has rights */
3632         r = castlingRights[moveNum][2] = initialRights[2];
3633         r = castlingRights[moveNum][5] = initialRights[5];
3634     }
3635     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3636     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3637
3638     
3639     if (ics_getting_history == H_GOT_REQ_HEADER ||
3640         ics_getting_history == H_GOT_UNREQ_HEADER) {
3641         /* This was an initial position from a move list, not
3642            the current position */
3643         return;
3644     }
3645     
3646     /* Update currentMove and known move number limits */
3647     newMove = newGame || moveNum > forwardMostMove;
3648
3649     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3650     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3651         takeback = forwardMostMove - moveNum;
3652         for (i = 0; i < takeback; i++) {
3653              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3654              SendToProgram("undo\n", &first);
3655         }
3656     }
3657
3658     if (newGame) {
3659         forwardMostMove = backwardMostMove = currentMove = moveNum;
3660         if (gameMode == IcsExamining && moveNum == 0) {
3661           /* Workaround for ICS limitation: we are not told the wild
3662              type when starting to examine a game.  But if we ask for
3663              the move list, the move list header will tell us */
3664             ics_getting_history = H_REQUESTED;
3665             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3666             SendToICS(str);
3667         }
3668     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3669                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3670         forwardMostMove = moveNum;
3671         if (!pausing || currentMove > forwardMostMove)
3672           currentMove = forwardMostMove;
3673     } else {
3674         /* New part of history that is not contiguous with old part */ 
3675         if (pausing && gameMode == IcsExamining) {
3676             pauseExamInvalid = TRUE;
3677             forwardMostMove = pauseExamForwardMostMove;
3678             return;
3679         }
3680         forwardMostMove = backwardMostMove = currentMove = moveNum;
3681         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3682             ics_getting_history = H_REQUESTED;
3683             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3684             SendToICS(str);
3685         }
3686     }
3687     
3688     /* Update the clocks */
3689     if (strchr(elapsed_time, '.')) {
3690       /* Time is in ms */
3691       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3692       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3693     } else {
3694       /* Time is in seconds */
3695       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3696       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3697     }
3698       
3699
3700 #if ZIPPY
3701     if (appData.zippyPlay && newGame &&
3702         gameMode != IcsObserving && gameMode != IcsIdle &&
3703         gameMode != IcsExamining)
3704       ZippyFirstBoard(moveNum, basetime, increment);
3705 #endif
3706     
3707     /* Put the move on the move list, first converting
3708        to canonical algebraic form. */
3709     if (moveNum > 0) {
3710   if (appData.debugMode) {
3711     if (appData.debugMode) { int f = forwardMostMove;
3712         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3713                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3714     }
3715     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3716     fprintf(debugFP, "moveNum = %d\n", moveNum);
3717     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3718     setbuf(debugFP, NULL);
3719   }
3720         if (moveNum <= backwardMostMove) {
3721             /* We don't know what the board looked like before
3722                this move.  Punt. */
3723             strcpy(parseList[moveNum - 1], move_str);
3724             strcat(parseList[moveNum - 1], " ");
3725             strcat(parseList[moveNum - 1], elapsed_time);
3726             moveList[moveNum - 1][0] = NULLCHAR;
3727         } else if (strcmp(move_str, "none") == 0) {
3728             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3729             /* Again, we don't know what the board looked like;
3730                this is really the start of the game. */
3731             parseList[moveNum - 1][0] = NULLCHAR;
3732             moveList[moveNum - 1][0] = NULLCHAR;
3733             backwardMostMove = moveNum;
3734             startedFromSetupPosition = TRUE;
3735             fromX = fromY = toX = toY = -1;
3736         } else {
3737           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3738           //                 So we parse the long-algebraic move string in stead of the SAN move
3739           int valid; char buf[MSG_SIZ], *prom;
3740
3741           // str looks something like "Q/a1-a2"; kill the slash
3742           if(str[1] == '/') 
3743                 sprintf(buf, "%c%s", str[0], str+2);
3744           else  strcpy(buf, str); // might be castling
3745           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3746                 strcat(buf, prom); // long move lacks promo specification!
3747           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3748                 if(appData.debugMode) 
3749                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3750                 strcpy(move_str, buf);
3751           }
3752           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3753                                 &fromX, &fromY, &toX, &toY, &promoChar)
3754                || ParseOneMove(buf, moveNum - 1, &moveType,
3755                                 &fromX, &fromY, &toX, &toY, &promoChar);
3756           // end of long SAN patch
3757           if (valid) {
3758             (void) CoordsToAlgebraic(boards[moveNum - 1],
3759                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3760                                      fromY, fromX, toY, toX, promoChar,
3761                                      parseList[moveNum-1]);
3762             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3763                              castlingRights[moveNum]) ) {
3764               case MT_NONE:
3765               case MT_STALEMATE:
3766               default:
3767                 break;
3768               case MT_CHECK:
3769                 if(gameInfo.variant != VariantShogi)
3770                     strcat(parseList[moveNum - 1], "+");
3771                 break;
3772               case MT_CHECKMATE:
3773               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3774                 strcat(parseList[moveNum - 1], "#");
3775                 break;
3776             }
3777             strcat(parseList[moveNum - 1], " ");
3778             strcat(parseList[moveNum - 1], elapsed_time);
3779             /* currentMoveString is set as a side-effect of ParseOneMove */
3780             strcpy(moveList[moveNum - 1], currentMoveString);
3781             strcat(moveList[moveNum - 1], "\n");
3782           } else {
3783             /* Move from ICS was illegal!?  Punt. */
3784   if (appData.debugMode) {
3785     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3786     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3787   }
3788             strcpy(parseList[moveNum - 1], move_str);
3789             strcat(parseList[moveNum - 1], " ");
3790             strcat(parseList[moveNum - 1], elapsed_time);
3791             moveList[moveNum - 1][0] = NULLCHAR;
3792             fromX = fromY = toX = toY = -1;
3793           }
3794         }
3795   if (appData.debugMode) {
3796     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3797     setbuf(debugFP, NULL);
3798   }
3799
3800 #if ZIPPY
3801         /* Send move to chess program (BEFORE animating it). */
3802         if (appData.zippyPlay && !newGame && newMove && 
3803            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3804
3805             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3806                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3807                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3808                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3809                             move_str);
3810                     DisplayError(str, 0);
3811                 } else {
3812                     if (first.sendTime) {
3813                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3814                     }
3815                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3816                     if (firstMove && !bookHit) {
3817                         firstMove = FALSE;
3818                         if (first.useColors) {
3819                           SendToProgram(gameMode == IcsPlayingWhite ?
3820                                         "white\ngo\n" :
3821                                         "black\ngo\n", &first);
3822                         } else {
3823                           SendToProgram("go\n", &first);
3824                         }
3825                         first.maybeThinking = TRUE;
3826                     }
3827                 }
3828             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3829               if (moveList[moveNum - 1][0] == NULLCHAR) {
3830                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3831                 DisplayError(str, 0);
3832               } else {
3833                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3834                 SendMoveToProgram(moveNum - 1, &first);
3835               }
3836             }
3837         }
3838 #endif
3839     }
3840
3841     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3842         /* If move comes from a remote source, animate it.  If it
3843            isn't remote, it will have already been animated. */
3844         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3845             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3846         }
3847         if (!pausing && appData.highlightLastMove) {
3848             SetHighlights(fromX, fromY, toX, toY);
3849         }
3850     }
3851     
3852     /* Start the clocks */
3853     whiteFlag = blackFlag = FALSE;
3854     appData.clockMode = !(basetime == 0 && increment == 0);
3855     if (ticking == 0) {
3856       ics_clock_paused = TRUE;
3857       StopClocks();
3858     } else if (ticking == 1) {
3859       ics_clock_paused = FALSE;
3860     }
3861     if (gameMode == IcsIdle ||
3862         relation == RELATION_OBSERVING_STATIC ||
3863         relation == RELATION_EXAMINING ||
3864         ics_clock_paused)
3865       DisplayBothClocks();
3866     else
3867       StartClocks();
3868     
3869     /* Display opponents and material strengths */
3870     if (gameInfo.variant != VariantBughouse &&
3871         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3872         if (tinyLayout || smallLayout) {
3873             if(gameInfo.variant == VariantNormal)
3874                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3875                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3876                     basetime, increment);
3877             else
3878                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3879                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3880                     basetime, increment, (int) gameInfo.variant);
3881         } else {
3882             if(gameInfo.variant == VariantNormal)
3883                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3884                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3885                     basetime, increment);
3886             else
3887                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3888                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3889                     basetime, increment, VariantName(gameInfo.variant));
3890         }
3891         DisplayTitle(str);
3892   if (appData.debugMode) {
3893     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3894   }
3895     }
3896
3897    
3898     /* Display the board */
3899     if (!pausing && !appData.noGUI) {
3900       
3901       if (appData.premove)
3902           if (!gotPremove || 
3903              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3904              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3905               ClearPremoveHighlights();
3906
3907       DrawPosition(FALSE, boards[currentMove]);
3908       DisplayMove(moveNum - 1);
3909       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3910             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3911               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3912         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3913       }
3914     }
3915
3916     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3917 #if ZIPPY
3918     if(bookHit) { // [HGM] book: simulate book reply
3919         static char bookMove[MSG_SIZ]; // a bit generous?
3920
3921         programStats.nodes = programStats.depth = programStats.time = 
3922         programStats.score = programStats.got_only_move = 0;
3923         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3924
3925         strcpy(bookMove, "move ");
3926         strcat(bookMove, bookHit);
3927         HandleMachineMove(bookMove, &first);
3928     }
3929 #endif
3930 }
3931
3932 void
3933 GetMoveListEvent()
3934 {
3935     char buf[MSG_SIZ];
3936     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3937         ics_getting_history = H_REQUESTED;
3938         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3939         SendToICS(buf);
3940     }
3941 }
3942
3943 void
3944 AnalysisPeriodicEvent(force)
3945      int force;
3946 {
3947     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3948          && !force) || !appData.periodicUpdates)
3949       return;
3950
3951     /* Send . command to Crafty to collect stats */
3952     SendToProgram(".\n", &first);
3953
3954     /* Don't send another until we get a response (this makes
3955        us stop sending to old Crafty's which don't understand
3956        the "." command (sending illegal cmds resets node count & time,
3957        which looks bad)) */
3958     programStats.ok_to_send = 0;
3959 }
3960
3961 void ics_update_width(new_width)
3962         int new_width;
3963 {
3964         ics_printf("set width %d\n", new_width);
3965 }
3966
3967 void
3968 SendMoveToProgram(moveNum, cps)
3969      int moveNum;
3970      ChessProgramState *cps;
3971 {
3972     char buf[MSG_SIZ];
3973
3974     if (cps->useUsermove) {
3975       SendToProgram("usermove ", cps);
3976     }
3977     if (cps->useSAN) {
3978       char *space;
3979       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3980         int len = space - parseList[moveNum];
3981         memcpy(buf, parseList[moveNum], len);
3982         buf[len++] = '\n';
3983         buf[len] = NULLCHAR;
3984       } else {
3985         sprintf(buf, "%s\n", parseList[moveNum]);
3986       }
3987       SendToProgram(buf, cps);
3988     } else {
3989       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3990         AlphaRank(moveList[moveNum], 4);
3991         SendToProgram(moveList[moveNum], cps);
3992         AlphaRank(moveList[moveNum], 4); // and back
3993       } else
3994       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3995        * the engine. It would be nice to have a better way to identify castle 
3996        * moves here. */
3997       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3998                                                                          && cps->useOOCastle) {
3999         int fromX = moveList[moveNum][0] - AAA; 
4000         int fromY = moveList[moveNum][1] - ONE;
4001         int toX = moveList[moveNum][2] - AAA; 
4002         int toY = moveList[moveNum][3] - ONE;
4003         if((boards[moveNum][fromY][fromX] == WhiteKing 
4004             && boards[moveNum][toY][toX] == WhiteRook)
4005            || (boards[moveNum][fromY][fromX] == BlackKing 
4006                && boards[moveNum][toY][toX] == BlackRook)) {
4007           if(toX > fromX) SendToProgram("O-O\n", cps);
4008           else SendToProgram("O-O-O\n", cps);
4009         }
4010         else SendToProgram(moveList[moveNum], cps);
4011       }
4012       else SendToProgram(moveList[moveNum], cps);
4013       /* End of additions by Tord */
4014     }
4015
4016     /* [HGM] setting up the opening has brought engine in force mode! */
4017     /*       Send 'go' if we are in a mode where machine should play. */
4018     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4019         (gameMode == TwoMachinesPlay   ||
4020 #ifdef ZIPPY
4021          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4022 #endif
4023          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4024         SendToProgram("go\n", cps);
4025   if (appData.debugMode) {
4026     fprintf(debugFP, "(extra)\n");
4027   }
4028     }
4029     setboardSpoiledMachineBlack = 0;
4030 }
4031
4032 void
4033 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4034      ChessMove moveType;
4035      int fromX, fromY, toX, toY;
4036 {
4037     char user_move[MSG_SIZ];
4038
4039     switch (moveType) {
4040       default:
4041         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4042                 (int)moveType, fromX, fromY, toX, toY);
4043         DisplayError(user_move + strlen("say "), 0);
4044         break;
4045       case WhiteKingSideCastle:
4046       case BlackKingSideCastle:
4047       case WhiteQueenSideCastleWild:
4048       case BlackQueenSideCastleWild:
4049       /* PUSH Fabien */
4050       case WhiteHSideCastleFR:
4051       case BlackHSideCastleFR:
4052       /* POP Fabien */
4053         sprintf(user_move, "o-o\n");
4054         break;
4055       case WhiteQueenSideCastle:
4056       case BlackQueenSideCastle:
4057       case WhiteKingSideCastleWild:
4058       case BlackKingSideCastleWild:
4059       /* PUSH Fabien */
4060       case WhiteASideCastleFR:
4061       case BlackASideCastleFR:
4062       /* POP Fabien */
4063         sprintf(user_move, "o-o-o\n");
4064         break;
4065       case WhitePromotionQueen:
4066       case BlackPromotionQueen:
4067       case WhitePromotionRook:
4068       case BlackPromotionRook:
4069       case WhitePromotionBishop:
4070       case BlackPromotionBishop:
4071       case WhitePromotionKnight:
4072       case BlackPromotionKnight:
4073       case WhitePromotionKing:
4074       case BlackPromotionKing:
4075       case WhitePromotionChancellor:
4076       case BlackPromotionChancellor:
4077       case WhitePromotionArchbishop:
4078       case BlackPromotionArchbishop:
4079         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4080             sprintf(user_move, "%c%c%c%c=%c\n",
4081                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4082                 PieceToChar(WhiteFerz));
4083         else if(gameInfo.variant == VariantGreat)
4084             sprintf(user_move, "%c%c%c%c=%c\n",
4085                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4086                 PieceToChar(WhiteMan));
4087         else
4088             sprintf(user_move, "%c%c%c%c=%c\n",
4089                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4090                 PieceToChar(PromoPiece(moveType)));
4091         break;
4092       case WhiteDrop:
4093       case BlackDrop:
4094         sprintf(user_move, "%c@%c%c\n",
4095                 ToUpper(PieceToChar((ChessSquare) fromX)),
4096                 AAA + toX, ONE + toY);
4097         break;
4098       case NormalMove:
4099       case WhiteCapturesEnPassant:
4100       case BlackCapturesEnPassant:
4101       case IllegalMove:  /* could be a variant we don't quite understand */
4102         sprintf(user_move, "%c%c%c%c\n",
4103                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4104         break;
4105     }
4106     SendToICS(user_move);
4107     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4108         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4109 }
4110
4111 void
4112 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4113      int rf, ff, rt, ft;
4114      char promoChar;
4115      char move[7];
4116 {
4117     if (rf == DROP_RANK) {
4118         sprintf(move, "%c@%c%c\n",
4119                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4120     } else {
4121         if (promoChar == 'x' || promoChar == NULLCHAR) {
4122             sprintf(move, "%c%c%c%c\n",
4123                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4124         } else {
4125             sprintf(move, "%c%c%c%c%c\n",
4126                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4127         }
4128     }
4129 }
4130
4131 void
4132 ProcessICSInitScript(f)
4133      FILE *f;
4134 {
4135     char buf[MSG_SIZ];
4136
4137     while (fgets(buf, MSG_SIZ, f)) {
4138         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4139     }
4140
4141     fclose(f);
4142 }
4143
4144
4145 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4146 void
4147 AlphaRank(char *move, int n)
4148 {
4149 //    char *p = move, c; int x, y;
4150
4151     if (appData.debugMode) {
4152         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4153     }
4154
4155     if(move[1]=='*' && 
4156        move[2]>='0' && move[2]<='9' &&
4157        move[3]>='a' && move[3]<='x'    ) {
4158         move[1] = '@';
4159         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4160         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4161     } else
4162     if(move[0]>='0' && move[0]<='9' &&
4163        move[1]>='a' && move[1]<='x' &&
4164        move[2]>='0' && move[2]<='9' &&
4165        move[3]>='a' && move[3]<='x'    ) {
4166         /* input move, Shogi -> normal */
4167         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4168         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4169         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4170         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4171     } else
4172     if(move[1]=='@' &&
4173        move[3]>='0' && move[3]<='9' &&
4174        move[2]>='a' && move[2]<='x'    ) {
4175         move[1] = '*';
4176         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4177         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4178     } else
4179     if(
4180        move[0]>='a' && move[0]<='x' &&
4181        move[3]>='0' && move[3]<='9' &&
4182        move[2]>='a' && move[2]<='x'    ) {
4183          /* output move, normal -> Shogi */
4184         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4185         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4186         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4187         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4188         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4189     }
4190     if (appData.debugMode) {
4191         fprintf(debugFP, "   out = '%s'\n", move);
4192     }
4193 }
4194
4195 /* Parser for moves from gnuchess, ICS, or user typein box */
4196 Boolean
4197 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4198      char *move;
4199      int moveNum;
4200      ChessMove *moveType;
4201      int *fromX, *fromY, *toX, *toY;
4202      char *promoChar;
4203 {       
4204     if (appData.debugMode) {
4205         fprintf(debugFP, "move to parse: %s\n", move);
4206     }
4207     *moveType = yylexstr(moveNum, move);
4208
4209     switch (*moveType) {
4210       case WhitePromotionChancellor:
4211       case BlackPromotionChancellor:
4212       case WhitePromotionArchbishop:
4213       case BlackPromotionArchbishop:
4214       case WhitePromotionQueen:
4215       case BlackPromotionQueen:
4216       case WhitePromotionRook:
4217       case BlackPromotionRook:
4218       case WhitePromotionBishop:
4219       case BlackPromotionBishop:
4220       case WhitePromotionKnight:
4221       case BlackPromotionKnight:
4222       case WhitePromotionKing:
4223       case BlackPromotionKing:
4224       case NormalMove:
4225       case WhiteCapturesEnPassant:
4226       case BlackCapturesEnPassant:
4227       case WhiteKingSideCastle:
4228       case WhiteQueenSideCastle:
4229       case BlackKingSideCastle:
4230       case BlackQueenSideCastle:
4231       case WhiteKingSideCastleWild:
4232       case WhiteQueenSideCastleWild:
4233       case BlackKingSideCastleWild:
4234       case BlackQueenSideCastleWild:
4235       /* Code added by Tord: */
4236       case WhiteHSideCastleFR:
4237       case WhiteASideCastleFR:
4238       case BlackHSideCastleFR:
4239       case BlackASideCastleFR:
4240       /* End of code added by Tord */
4241       case IllegalMove:         /* bug or odd chess variant */
4242         *fromX = currentMoveString[0] - AAA;
4243         *fromY = currentMoveString[1] - ONE;
4244         *toX = currentMoveString[2] - AAA;
4245         *toY = currentMoveString[3] - ONE;
4246         *promoChar = currentMoveString[4];
4247         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4248             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4249     if (appData.debugMode) {
4250         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4251     }
4252             *fromX = *fromY = *toX = *toY = 0;
4253             return FALSE;
4254         }
4255         if (appData.testLegality) {
4256           return (*moveType != IllegalMove);
4257         } else {
4258           return !(fromX == fromY && toX == toY);
4259         }
4260
4261       case WhiteDrop:
4262       case BlackDrop:
4263         *fromX = *moveType == WhiteDrop ?
4264           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4265           (int) CharToPiece(ToLower(currentMoveString[0]));
4266         *fromY = DROP_RANK;
4267         *toX = currentMoveString[2] - AAA;
4268         *toY = currentMoveString[3] - ONE;
4269         *promoChar = NULLCHAR;
4270         return TRUE;
4271
4272       case AmbiguousMove:
4273       case ImpossibleMove:
4274       case (ChessMove) 0:       /* end of file */
4275       case ElapsedTime:
4276       case Comment:
4277       case PGNTag:
4278       case NAG:
4279       case WhiteWins:
4280       case BlackWins:
4281       case GameIsDrawn:
4282       default:
4283     if (appData.debugMode) {
4284         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4285     }
4286         /* bug? */
4287         *fromX = *fromY = *toX = *toY = 0;
4288         *promoChar = NULLCHAR;
4289         return FALSE;
4290     }
4291 }
4292
4293 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4294 // All positions will have equal probability, but the current method will not provide a unique
4295 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4296 #define DARK 1
4297 #define LITE 2
4298 #define ANY 3
4299
4300 int squaresLeft[4];
4301 int piecesLeft[(int)BlackPawn];
4302 int seed, nrOfShuffles;
4303
4304 void GetPositionNumber()
4305 {       // sets global variable seed
4306         int i;
4307
4308         seed = appData.defaultFrcPosition;
4309         if(seed < 0) { // randomize based on time for negative FRC position numbers
4310                 for(i=0; i<50; i++) seed += random();
4311                 seed = random() ^ random() >> 8 ^ random() << 8;
4312                 if(seed<0) seed = -seed;
4313         }
4314 }
4315
4316 int put(Board board, int pieceType, int rank, int n, int shade)
4317 // put the piece on the (n-1)-th empty squares of the given shade
4318 {
4319         int i;
4320
4321         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4322                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4323                         board[rank][i] = (ChessSquare) pieceType;
4324                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4325                         squaresLeft[ANY]--;
4326                         piecesLeft[pieceType]--; 
4327                         return i;
4328                 }
4329         }
4330         return -1;
4331 }
4332
4333
4334 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4335 // calculate where the next piece goes, (any empty square), and put it there
4336 {
4337         int i;
4338
4339         i = seed % squaresLeft[shade];
4340         nrOfShuffles *= squaresLeft[shade];
4341         seed /= squaresLeft[shade];
4342         put(board, pieceType, rank, i, shade);
4343 }
4344
4345 void AddTwoPieces(Board board, int pieceType, int rank)
4346 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4347 {
4348         int i, n=squaresLeft[ANY], j=n-1, k;
4349
4350         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4351         i = seed % k;  // pick one
4352         nrOfShuffles *= k;
4353         seed /= k;
4354         while(i >= j) i -= j--;
4355         j = n - 1 - j; i += j;
4356         put(board, pieceType, rank, j, ANY);
4357         put(board, pieceType, rank, i, ANY);
4358 }
4359
4360 void SetUpShuffle(Board board, int number)
4361 {
4362         int i, p, first=1;
4363
4364         GetPositionNumber(); nrOfShuffles = 1;
4365
4366         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4367         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4368         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4369
4370         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4371
4372         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4373             p = (int) board[0][i];
4374             if(p < (int) BlackPawn) piecesLeft[p] ++;
4375             board[0][i] = EmptySquare;
4376         }
4377
4378         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4379             // shuffles restricted to allow normal castling put KRR first
4380             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4381                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4382             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4383                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4384             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4385                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4386             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4387                 put(board, WhiteRook, 0, 0, ANY);
4388             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4389         }
4390
4391         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4392             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4393             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4394                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4395                 while(piecesLeft[p] >= 2) {
4396                     AddOnePiece(board, p, 0, LITE);
4397                     AddOnePiece(board, p, 0, DARK);
4398                 }
4399                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4400             }
4401
4402         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4403             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4404             // but we leave King and Rooks for last, to possibly obey FRC restriction
4405             if(p == (int)WhiteRook) continue;
4406             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4407             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4408         }
4409
4410         // now everything is placed, except perhaps King (Unicorn) and Rooks
4411
4412         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4413             // Last King gets castling rights
4414             while(piecesLeft[(int)WhiteUnicorn]) {
4415                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4416                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4417             }
4418
4419             while(piecesLeft[(int)WhiteKing]) {
4420                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4421                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4422             }
4423
4424
4425         } else {
4426             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4427             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4428         }
4429
4430         // Only Rooks can be left; simply place them all
4431         while(piecesLeft[(int)WhiteRook]) {
4432                 i = put(board, WhiteRook, 0, 0, ANY);
4433                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4434                         if(first) {
4435                                 first=0;
4436                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4437                         }
4438                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4439                 }
4440         }
4441         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4442             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4443         }
4444
4445         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4446 }
4447
4448 int SetCharTable( char *table, const char * map )
4449 /* [HGM] moved here from winboard.c because of its general usefulness */
4450 /*       Basically a safe strcpy that uses the last character as King */
4451 {
4452     int result = FALSE; int NrPieces;
4453
4454     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4455                     && NrPieces >= 12 && !(NrPieces&1)) {
4456         int i; /* [HGM] Accept even length from 12 to 34 */
4457
4458         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4459         for( i=0; i<NrPieces/2-1; i++ ) {
4460             table[i] = map[i];
4461             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4462         }
4463         table[(int) WhiteKing]  = map[NrPieces/2-1];
4464         table[(int) BlackKing]  = map[NrPieces-1];
4465
4466         result = TRUE;
4467     }
4468
4469     return result;
4470 }
4471
4472 void Prelude(Board board)
4473 {       // [HGM] superchess: random selection of exo-pieces
4474         int i, j, k; ChessSquare p; 
4475         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4476
4477         GetPositionNumber(); // use FRC position number
4478
4479         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4480             SetCharTable(pieceToChar, appData.pieceToCharTable);
4481             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4482                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4483         }
4484
4485         j = seed%4;                 seed /= 4; 
4486         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3 + (seed%3 >= j); seed /= 3; 
4490         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4491         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4492         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4493         j = seed%3;                 seed /= 3; 
4494         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4495         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4496         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4497         j = seed%2 + (seed%2 >= j); seed /= 2; 
4498         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4499         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4500         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4501         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4502         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4503         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4504         put(board, exoPieces[0],    0, 0, ANY);
4505         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4506 }
4507
4508 void
4509 InitPosition(redraw)
4510      int redraw;
4511 {
4512     ChessSquare (* pieces)[BOARD_SIZE];
4513     int i, j, pawnRow, overrule,
4514     oldx = gameInfo.boardWidth,
4515     oldy = gameInfo.boardHeight,
4516     oldh = gameInfo.holdingsWidth,
4517     oldv = gameInfo.variant;
4518
4519     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4520
4521     /* [AS] Initialize pv info list [HGM] and game status */
4522     {
4523         for( i=0; i<MAX_MOVES; i++ ) {
4524             pvInfoList[i].depth = 0;
4525             epStatus[i]=EP_NONE;
4526             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4527         }
4528
4529         initialRulePlies = 0; /* 50-move counter start */
4530
4531         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4532         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4533     }
4534
4535     
4536     /* [HGM] logic here is completely changed. In stead of full positions */
4537     /* the initialized data only consist of the two backranks. The switch */
4538     /* selects which one we will use, which is than copied to the Board   */
4539     /* initialPosition, which for the rest is initialized by Pawns and    */
4540     /* empty squares. This initial position is then copied to boards[0],  */
4541     /* possibly after shuffling, so that it remains available.            */
4542
4543     gameInfo.holdingsWidth = 0; /* default board sizes */
4544     gameInfo.boardWidth    = 8;
4545     gameInfo.boardHeight   = 8;
4546     gameInfo.holdingsSize  = 0;
4547     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4548     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4549     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4550
4551     switch (gameInfo.variant) {
4552     case VariantFischeRandom:
4553       shuffleOpenings = TRUE;
4554     default:
4555       pieces = FIDEArray;
4556       break;
4557     case VariantShatranj:
4558       pieces = ShatranjArray;
4559       nrCastlingRights = 0;
4560       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4561       break;
4562     case VariantTwoKings:
4563       pieces = twoKingsArray;
4564       break;
4565     case VariantCapaRandom:
4566       shuffleOpenings = TRUE;
4567     case VariantCapablanca:
4568       pieces = CapablancaArray;
4569       gameInfo.boardWidth = 10;
4570       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4571       break;
4572     case VariantGothic:
4573       pieces = GothicArray;
4574       gameInfo.boardWidth = 10;
4575       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4576       break;
4577     case VariantJanus:
4578       pieces = JanusArray;
4579       gameInfo.boardWidth = 10;
4580       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4581       nrCastlingRights = 6;
4582         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4583         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4584         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4585         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4586         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4587         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4588       break;
4589     case VariantFalcon:
4590       pieces = FalconArray;
4591       gameInfo.boardWidth = 10;
4592       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4593       break;
4594     case VariantXiangqi:
4595       pieces = XiangqiArray;
4596       gameInfo.boardWidth  = 9;
4597       gameInfo.boardHeight = 10;
4598       nrCastlingRights = 0;
4599       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4600       break;
4601     case VariantShogi:
4602       pieces = ShogiArray;
4603       gameInfo.boardWidth  = 9;
4604       gameInfo.boardHeight = 9;
4605       gameInfo.holdingsSize = 7;
4606       nrCastlingRights = 0;
4607       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4608       break;
4609     case VariantCourier:
4610       pieces = CourierArray;
4611       gameInfo.boardWidth  = 12;
4612       nrCastlingRights = 0;
4613       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4614       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4615       break;
4616     case VariantKnightmate:
4617       pieces = KnightmateArray;
4618       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4619       break;
4620     case VariantFairy:
4621       pieces = fairyArray;
4622       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4623       break;
4624     case VariantGreat:
4625       pieces = GreatArray;
4626       gameInfo.boardWidth = 10;
4627       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4628       gameInfo.holdingsSize = 8;
4629       break;
4630     case VariantSuper:
4631       pieces = FIDEArray;
4632       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4633       gameInfo.holdingsSize = 8;
4634       startedFromSetupPosition = TRUE;
4635       break;
4636     case VariantCrazyhouse:
4637     case VariantBughouse:
4638       pieces = FIDEArray;
4639       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4640       gameInfo.holdingsSize = 5;
4641       break;
4642     case VariantWildCastle:
4643       pieces = FIDEArray;
4644       /* !!?shuffle with kings guaranteed to be on d or e file */
4645       shuffleOpenings = 1;
4646       break;
4647     case VariantNoCastle:
4648       pieces = FIDEArray;
4649       nrCastlingRights = 0;
4650       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4651       /* !!?unconstrained back-rank shuffle */
4652       shuffleOpenings = 1;
4653       break;
4654     }
4655
4656     overrule = 0;
4657     if(appData.NrFiles >= 0) {
4658         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4659         gameInfo.boardWidth = appData.NrFiles;
4660     }
4661     if(appData.NrRanks >= 0) {
4662         gameInfo.boardHeight = appData.NrRanks;
4663     }
4664     if(appData.holdingsSize >= 0) {
4665         i = appData.holdingsSize;
4666         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4667         gameInfo.holdingsSize = i;
4668     }
4669     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4670     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4671         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4672
4673     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4674     if(pawnRow < 1) pawnRow = 1;
4675
4676     /* User pieceToChar list overrules defaults */
4677     if(appData.pieceToCharTable != NULL)
4678         SetCharTable(pieceToChar, appData.pieceToCharTable);
4679
4680     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4681
4682         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4683             s = (ChessSquare) 0; /* account holding counts in guard band */
4684         for( i=0; i<BOARD_HEIGHT; i++ )
4685             initialPosition[i][j] = s;
4686
4687         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4688         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4689         initialPosition[pawnRow][j] = WhitePawn;
4690         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4691         if(gameInfo.variant == VariantXiangqi) {
4692             if(j&1) {
4693                 initialPosition[pawnRow][j] = 
4694                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4695                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4696                    initialPosition[2][j] = WhiteCannon;
4697                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4698                 }
4699             }
4700         }
4701         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4702     }
4703     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4704
4705             j=BOARD_LEFT+1;
4706             initialPosition[1][j] = WhiteBishop;
4707             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4708             j=BOARD_RGHT-2;
4709             initialPosition[1][j] = WhiteRook;
4710             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4711     }
4712
4713     if( nrCastlingRights == -1) {
4714         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4715         /*       This sets default castling rights from none to normal corners   */
4716         /* Variants with other castling rights must set them themselves above    */
4717         nrCastlingRights = 6;
4718        
4719         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4720         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4721         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4722         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4723         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4724         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4725      }
4726
4727      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4728      if(gameInfo.variant == VariantGreat) { // promotion commoners
4729         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4730         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4731         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4732         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4733      }
4734   if (appData.debugMode) {
4735     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4736   }
4737     if(shuffleOpenings) {
4738         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4739         startedFromSetupPosition = TRUE;
4740     }
4741     if(startedFromPositionFile) {
4742       /* [HGM] loadPos: use PositionFile for every new game */
4743       CopyBoard(initialPosition, filePosition);
4744       for(i=0; i<nrCastlingRights; i++)
4745           castlingRights[0][i] = initialRights[i] = fileRights[i];
4746       startedFromSetupPosition = TRUE;
4747     }
4748
4749     CopyBoard(boards[0], initialPosition);
4750
4751     if(oldx != gameInfo.boardWidth ||
4752        oldy != gameInfo.boardHeight ||
4753        oldh != gameInfo.holdingsWidth
4754 #ifdef GOTHIC
4755        || oldv == VariantGothic ||        // For licensing popups
4756        gameInfo.variant == VariantGothic
4757 #endif
4758 #ifdef FALCON
4759        || oldv == VariantFalcon ||
4760        gameInfo.variant == VariantFalcon
4761 #endif
4762                                          )
4763             InitDrawingSizes(-2 ,0);
4764
4765     if (redraw)
4766       DrawPosition(TRUE, boards[currentMove]);
4767 }
4768
4769 void
4770 SendBoard(cps, moveNum)
4771      ChessProgramState *cps;
4772      int moveNum;
4773 {
4774     char message[MSG_SIZ];
4775     
4776     if (cps->useSetboard) {
4777       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4778       sprintf(message, "setboard %s\n", fen);
4779       SendToProgram(message, cps);
4780       free(fen);
4781
4782     } else {
4783       ChessSquare *bp;
4784       int i, j;
4785       /* Kludge to set black to move, avoiding the troublesome and now
4786        * deprecated "black" command.
4787        */
4788       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4789
4790       SendToProgram("edit\n", cps);
4791       SendToProgram("#\n", cps);
4792       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4793         bp = &boards[moveNum][i][BOARD_LEFT];
4794         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4795           if ((int) *bp < (int) BlackPawn) {
4796             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4797                     AAA + j, ONE + i);
4798             if(message[0] == '+' || message[0] == '~') {
4799                 sprintf(message, "%c%c%c+\n",
4800                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4801                         AAA + j, ONE + i);
4802             }
4803             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4804                 message[1] = BOARD_RGHT   - 1 - j + '1';
4805                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4806             }
4807             SendToProgram(message, cps);
4808           }
4809         }
4810       }
4811     
4812       SendToProgram("c\n", cps);
4813       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4814         bp = &boards[moveNum][i][BOARD_LEFT];
4815         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4816           if (((int) *bp != (int) EmptySquare)
4817               && ((int) *bp >= (int) BlackPawn)) {
4818             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4819                     AAA + j, ONE + i);
4820             if(message[0] == '+' || message[0] == '~') {
4821                 sprintf(message, "%c%c%c+\n",
4822                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4823                         AAA + j, ONE + i);
4824             }
4825             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4826                 message[1] = BOARD_RGHT   - 1 - j + '1';
4827                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4828             }
4829             SendToProgram(message, cps);
4830           }
4831         }
4832       }
4833     
4834       SendToProgram(".\n", cps);
4835     }
4836     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4837 }
4838
4839 int
4840 IsPromotion(fromX, fromY, toX, toY)
4841      int fromX, fromY, toX, toY;
4842 {
4843     /* [HGM] add Shogi promotions */
4844     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4845     ChessSquare piece;
4846
4847     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4848       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4849    /* [HGM] Note to self: line above also weeds out drops */
4850     piece = boards[currentMove][fromY][fromX];
4851     if(gameInfo.variant == VariantShogi) {
4852         promotionZoneSize = 3;
4853         highestPromotingPiece = (int)WhiteKing;
4854         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4855            and if in normal chess we then allow promotion to King, why not
4856            allow promotion of other piece in Shogi?                         */
4857     }
4858     if((int)piece >= BlackPawn) {
4859         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4860              return FALSE;
4861         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4862     } else {
4863         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4864            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4865     }
4866     return ( (int)piece <= highestPromotingPiece );
4867 }
4868
4869 int
4870 InPalace(row, column)
4871      int row, column;
4872 {   /* [HGM] for Xiangqi */
4873     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4874          column < (BOARD_WIDTH + 4)/2 &&
4875          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4876     return FALSE;
4877 }
4878
4879 int
4880 PieceForSquare (x, y)
4881      int x;
4882      int y;
4883 {
4884   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4885      return -1;
4886   else
4887      return boards[currentMove][y][x];
4888 }
4889
4890 int
4891 OKToStartUserMove(x, y)
4892      int x, y;
4893 {
4894     ChessSquare from_piece;
4895     int white_piece;
4896
4897     if (matchMode) return FALSE;
4898     if (gameMode == EditPosition) return TRUE;
4899
4900     if (x >= 0 && y >= 0)
4901       from_piece = boards[currentMove][y][x];
4902     else
4903       from_piece = EmptySquare;
4904
4905     if (from_piece == EmptySquare) return FALSE;
4906
4907     white_piece = (int)from_piece >= (int)WhitePawn &&
4908       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4909
4910     switch (gameMode) {
4911       case PlayFromGameFile:
4912       case AnalyzeFile:
4913       case TwoMachinesPlay:
4914       case EndOfGame:
4915         return FALSE;
4916
4917       case IcsObserving:
4918       case IcsIdle:
4919         return FALSE;
4920
4921       case MachinePlaysWhite:
4922       case IcsPlayingBlack:
4923         if (appData.zippyPlay) return FALSE;
4924         if (white_piece) {
4925             DisplayMoveError(_("You are playing Black"));
4926             return FALSE;
4927         }
4928         break;
4929
4930       case MachinePlaysBlack:
4931       case IcsPlayingWhite:
4932         if (appData.zippyPlay) return FALSE;
4933         if (!white_piece) {
4934             DisplayMoveError(_("You are playing White"));
4935             return FALSE;
4936         }
4937         break;
4938
4939       case EditGame:
4940         if (!white_piece && WhiteOnMove(currentMove)) {
4941             DisplayMoveError(_("It is White's turn"));
4942             return FALSE;
4943         }           
4944         if (white_piece && !WhiteOnMove(currentMove)) {
4945             DisplayMoveError(_("It is Black's turn"));
4946             return FALSE;
4947         }           
4948         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4949             /* Editing correspondence game history */
4950             /* Could disallow this or prompt for confirmation */
4951             cmailOldMove = -1;
4952         }
4953         if (currentMove < forwardMostMove) {
4954             /* Discarding moves */
4955             /* Could prompt for confirmation here,
4956                but I don't think that's such a good idea */
4957             forwardMostMove = currentMove;
4958         }
4959         break;
4960
4961       case BeginningOfGame:
4962         if (appData.icsActive) return FALSE;
4963         if (!appData.noChessProgram) {
4964             if (!white_piece) {
4965                 DisplayMoveError(_("You are playing White"));
4966                 return FALSE;
4967             }
4968         }
4969         break;
4970         
4971       case Training:
4972         if (!white_piece && WhiteOnMove(currentMove)) {
4973             DisplayMoveError(_("It is White's turn"));
4974             return FALSE;
4975         }           
4976         if (white_piece && !WhiteOnMove(currentMove)) {
4977             DisplayMoveError(_("It is Black's turn"));
4978             return FALSE;
4979         }           
4980         break;
4981
4982       default:
4983       case IcsExamining:
4984         break;
4985     }
4986     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4987         && gameMode != AnalyzeFile && gameMode != Training) {
4988         DisplayMoveError(_("Displayed position is not current"));
4989         return FALSE;
4990     }
4991     return TRUE;
4992 }
4993
4994 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4995 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4996 int lastLoadGameUseList = FALSE;
4997 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4998 ChessMove lastLoadGameStart = (ChessMove) 0;
4999
5000 ChessMove
5001 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5002      int fromX, fromY, toX, toY;
5003      int promoChar;
5004      Boolean captureOwn;
5005 {
5006     ChessMove moveType;
5007     ChessSquare pdown, pup;
5008
5009     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5010
5011     /* [HGM] suppress all moves into holdings area and guard band */
5012     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5013             return ImpossibleMove;
5014
5015     /* [HGM] <sameColor> moved to here from winboard.c */
5016     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5017     pdown = boards[currentMove][fromY][fromX];
5018     pup = boards[currentMove][toY][toX];
5019     if (    gameMode != EditPosition && !captureOwn &&
5020             (WhitePawn <= pdown && pdown < BlackPawn &&
5021              WhitePawn <= pup && pup < BlackPawn  ||
5022              BlackPawn <= pdown && pdown < EmptySquare &&
5023              BlackPawn <= pup && pup < EmptySquare 
5024             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5025                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5026                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5027                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5028                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5029         )           )
5030          return Comment;
5031
5032     /* Check if the user is playing in turn.  This is complicated because we
5033        let the user "pick up" a piece before it is his turn.  So the piece he
5034        tried to pick up may have been captured by the time he puts it down!
5035        Therefore we use the color the user is supposed to be playing in this
5036        test, not the color of the piece that is currently on the starting
5037        square---except in EditGame mode, where the user is playing both
5038        sides; fortunately there the capture race can't happen.  (It can
5039        now happen in IcsExamining mode, but that's just too bad.  The user
5040        will get a somewhat confusing message in that case.)
5041        */
5042
5043     switch (gameMode) {
5044       case PlayFromGameFile:
5045       case AnalyzeFile:
5046       case TwoMachinesPlay:
5047       case EndOfGame:
5048       case IcsObserving:
5049       case IcsIdle:
5050         /* We switched into a game mode where moves are not accepted,
5051            perhaps while the mouse button was down. */
5052         return ImpossibleMove;
5053
5054       case MachinePlaysWhite:
5055         /* User is moving for Black */
5056         if (WhiteOnMove(currentMove)) {
5057             DisplayMoveError(_("It is White's turn"));
5058             return ImpossibleMove;
5059         }
5060         break;
5061
5062       case MachinePlaysBlack:
5063         /* User is moving for White */
5064         if (!WhiteOnMove(currentMove)) {
5065             DisplayMoveError(_("It is Black's turn"));
5066             return ImpossibleMove;
5067         }
5068         break;
5069
5070       case EditGame:
5071       case IcsExamining:
5072       case BeginningOfGame:
5073       case AnalyzeMode:
5074       case Training:
5075         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5076             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5077             /* User is moving for Black */
5078             if (WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is White's turn"));
5080                 return ImpossibleMove;
5081             }
5082         } else {
5083             /* User is moving for White */
5084             if (!WhiteOnMove(currentMove)) {
5085                 DisplayMoveError(_("It is Black's turn"));
5086                 return ImpossibleMove;
5087             }
5088         }
5089         break;
5090
5091       case IcsPlayingBlack:
5092         /* User is moving for Black */
5093         if (WhiteOnMove(currentMove)) {
5094             if (!appData.premove) {
5095                 DisplayMoveError(_("It is White's turn"));
5096             } else if (toX >= 0 && toY >= 0) {
5097                 premoveToX = toX;
5098                 premoveToY = toY;
5099                 premoveFromX = fromX;
5100                 premoveFromY = fromY;
5101                 premovePromoChar = promoChar;
5102                 gotPremove = 1;
5103                 if (appData.debugMode) 
5104                     fprintf(debugFP, "Got premove: fromX %d,"
5105                             "fromY %d, toX %d, toY %d\n",
5106                             fromX, fromY, toX, toY);
5107             }
5108             return ImpossibleMove;
5109         }
5110         break;
5111
5112       case IcsPlayingWhite:
5113         /* User is moving for White */
5114         if (!WhiteOnMove(currentMove)) {
5115             if (!appData.premove) {
5116                 DisplayMoveError(_("It is Black's turn"));
5117             } else if (toX >= 0 && toY >= 0) {
5118                 premoveToX = toX;
5119                 premoveToY = toY;
5120                 premoveFromX = fromX;
5121                 premoveFromY = fromY;
5122                 premovePromoChar = promoChar;
5123                 gotPremove = 1;
5124                 if (appData.debugMode) 
5125                     fprintf(debugFP, "Got premove: fromX %d,"
5126                             "fromY %d, toX %d, toY %d\n",
5127                             fromX, fromY, toX, toY);
5128             }
5129             return ImpossibleMove;
5130         }
5131         break;
5132
5133       default:
5134         break;
5135
5136       case EditPosition:
5137         /* EditPosition, empty square, or different color piece;
5138            click-click move is possible */
5139         if (toX == -2 || toY == -2) {
5140             boards[0][fromY][fromX] = EmptySquare;
5141             return AmbiguousMove;
5142         } else if (toX >= 0 && toY >= 0) {
5143             boards[0][toY][toX] = boards[0][fromY][fromX];
5144             boards[0][fromY][fromX] = EmptySquare;
5145             return AmbiguousMove;
5146         }
5147         return ImpossibleMove;
5148     }
5149
5150     /* [HGM] If move started in holdings, it means a drop */
5151     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5152          if( pup != EmptySquare ) return ImpossibleMove;
5153          if(appData.testLegality) {
5154              /* it would be more logical if LegalityTest() also figured out
5155               * which drops are legal. For now we forbid pawns on back rank.
5156               * Shogi is on its own here...
5157               */
5158              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5159                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5160                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5161          }
5162          return WhiteDrop; /* Not needed to specify white or black yet */
5163     }
5164
5165     userOfferedDraw = FALSE;
5166         
5167     /* [HGM] always test for legality, to get promotion info */
5168     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5169                           epStatus[currentMove], castlingRights[currentMove],
5170                                          fromY, fromX, toY, toX, promoChar);
5171     /* [HGM] but possibly ignore an IllegalMove result */
5172     if (appData.testLegality) {
5173         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5174             DisplayMoveError(_("Illegal move"));
5175             return ImpossibleMove;
5176         }
5177     }
5178 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5179     return moveType;
5180     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5181        function is made into one that returns an OK move type if FinishMove
5182        should be called. This to give the calling driver routine the
5183        opportunity to finish the userMove input with a promotion popup,
5184        without bothering the user with this for invalid or illegal moves */
5185
5186 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5187 }
5188
5189 /* Common tail of UserMoveEvent and DropMenuEvent */
5190 int
5191 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5192      ChessMove moveType;
5193      int fromX, fromY, toX, toY;
5194      /*char*/int promoChar;
5195 {
5196     char *bookHit = 0;
5197 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5198     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5199         // [HGM] superchess: suppress promotions to non-available piece
5200         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5201         if(WhiteOnMove(currentMove)) {
5202             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5203         } else {
5204             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5205         }
5206     }
5207
5208     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5209        move type in caller when we know the move is a legal promotion */
5210     if(moveType == NormalMove && promoChar)
5211         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5212 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5213     /* [HGM] convert drag-and-drop piece drops to standard form */
5214     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5215          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5216            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5217                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5218 //         fromX = boards[currentMove][fromY][fromX];
5219            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5220            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5221            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5222            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5223          fromY = DROP_RANK;
5224     }
5225
5226     /* [HGM] <popupFix> The following if has been moved here from
5227        UserMoveEvent(). Because it seemed to belon here (why not allow
5228        piece drops in training games?), and because it can only be
5229        performed after it is known to what we promote. */
5230     if (gameMode == Training) {
5231       /* compare the move played on the board to the next move in the
5232        * game. If they match, display the move and the opponent's response. 
5233        * If they don't match, display an error message.
5234        */
5235       int saveAnimate;
5236       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5237       CopyBoard(testBoard, boards[currentMove]);
5238       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5239
5240       if (CompareBoards(testBoard, boards[currentMove+1])) {
5241         ForwardInner(currentMove+1);
5242
5243         /* Autoplay the opponent's response.
5244          * if appData.animate was TRUE when Training mode was entered,
5245          * the response will be animated.
5246          */
5247         saveAnimate = appData.animate;
5248         appData.animate = animateTraining;
5249         ForwardInner(currentMove+1);
5250         appData.animate = saveAnimate;
5251
5252         /* check for the end of the game */
5253         if (currentMove >= forwardMostMove) {
5254           gameMode = PlayFromGameFile;
5255           ModeHighlight();
5256           SetTrainingModeOff();
5257           DisplayInformation(_("End of game"));
5258         }
5259       } else {
5260         DisplayError(_("Incorrect move"), 0);
5261       }
5262       return 1;
5263     }
5264
5265   /* Ok, now we know that the move is good, so we can kill
5266      the previous line in Analysis Mode */
5267   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5268     forwardMostMove = currentMove;
5269   }
5270
5271   /* If we need the chess program but it's dead, restart it */
5272   ResurrectChessProgram();
5273
5274   /* A user move restarts a paused game*/
5275   if (pausing)
5276     PauseEvent();
5277
5278   thinkOutput[0] = NULLCHAR;
5279
5280   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5281
5282   if (gameMode == BeginningOfGame) {
5283     if (appData.noChessProgram) {
5284       gameMode = EditGame;
5285       SetGameInfo();
5286     } else {
5287       char buf[MSG_SIZ];
5288       gameMode = MachinePlaysBlack;
5289       StartClocks();
5290       SetGameInfo();
5291       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5292       DisplayTitle(buf);
5293       if (first.sendName) {
5294         sprintf(buf, "name %s\n", gameInfo.white);
5295         SendToProgram(buf, &first);
5296       }
5297       StartClocks();
5298     }
5299     ModeHighlight();
5300   }
5301 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5302   /* Relay move to ICS or chess engine */
5303   if (appData.icsActive) {
5304     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5305         gameMode == IcsExamining) {
5306       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5307       ics_user_moved = 1;
5308     }
5309   } else {
5310     if (first.sendTime && (gameMode == BeginningOfGame ||
5311                            gameMode == MachinePlaysWhite ||
5312                            gameMode == MachinePlaysBlack)) {
5313       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5314     }
5315     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5316          // [HGM] book: if program might be playing, let it use book
5317         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5318         first.maybeThinking = TRUE;
5319     } else SendMoveToProgram(forwardMostMove-1, &first);
5320     if (currentMove == cmailOldMove + 1) {
5321       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5322     }
5323   }
5324
5325   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5326
5327   switch (gameMode) {
5328   case EditGame:
5329     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5330                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5331     case MT_NONE:
5332     case MT_CHECK:
5333       break;
5334     case MT_CHECKMATE:
5335     case MT_STAINMATE:
5336       if (WhiteOnMove(currentMove)) {
5337         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5338       } else {
5339         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5340       }
5341       break;
5342     case MT_STALEMATE:
5343       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5344       break;
5345     }
5346     break;
5347     
5348   case MachinePlaysBlack:
5349   case MachinePlaysWhite:
5350     /* disable certain menu options while machine is thinking */
5351     SetMachineThinkingEnables();
5352     break;
5353
5354   default:
5355     break;
5356   }
5357
5358   if(bookHit) { // [HGM] book: simulate book reply
5359         static char bookMove[MSG_SIZ]; // a bit generous?
5360
5361         programStats.nodes = programStats.depth = programStats.time = 
5362         programStats.score = programStats.got_only_move = 0;
5363         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5364
5365         strcpy(bookMove, "move ");
5366         strcat(bookMove, bookHit);
5367         HandleMachineMove(bookMove, &first);
5368   }
5369   return 1;
5370 }
5371
5372 void
5373 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5374      int fromX, fromY, toX, toY;
5375      int promoChar;
5376 {
5377     /* [HGM] This routine was added to allow calling of its two logical
5378        parts from other modules in the old way. Before, UserMoveEvent()
5379        automatically called FinishMove() if the move was OK, and returned
5380        otherwise. I separated the two, in order to make it possible to
5381        slip a promotion popup in between. But that it always needs two
5382        calls, to the first part, (now called UserMoveTest() ), and to
5383        FinishMove if the first part succeeded. Calls that do not need
5384        to do anything in between, can call this routine the old way. 
5385     */
5386     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5387 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5388     if(moveType == AmbiguousMove)
5389         DrawPosition(FALSE, boards[currentMove]);
5390     else if(moveType != ImpossibleMove && moveType != Comment)
5391         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5392 }
5393
5394 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5395 {
5396 //    char * hint = lastHint;
5397     FrontEndProgramStats stats;
5398
5399     stats.which = cps == &first ? 0 : 1;
5400     stats.depth = cpstats->depth;
5401     stats.nodes = cpstats->nodes;
5402     stats.score = cpstats->score;
5403     stats.time = cpstats->time;
5404     stats.pv = cpstats->movelist;
5405     stats.hint = lastHint;
5406     stats.an_move_index = 0;
5407     stats.an_move_count = 0;
5408
5409     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5410         stats.hint = cpstats->move_name;
5411         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5412         stats.an_move_count = cpstats->nr_moves;
5413     }
5414
5415     SetProgramStats( &stats );
5416 }
5417
5418 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5419 {   // [HGM] book: this routine intercepts moves to simulate book replies
5420     char *bookHit = NULL;
5421
5422     //first determine if the incoming move brings opponent into his book
5423     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5424         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5425     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5426     if(bookHit != NULL && !cps->bookSuspend) {
5427         // make sure opponent is not going to reply after receiving move to book position
5428         SendToProgram("force\n", cps);
5429         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5430     }
5431     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5432     // now arrange restart after book miss
5433     if(bookHit) {
5434         // after a book hit we never send 'go', and the code after the call to this routine
5435         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5436         char buf[MSG_SIZ];
5437         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5438         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5439         SendToProgram(buf, cps);
5440         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5441     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5442         SendToProgram("go\n", cps);
5443         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5444     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5445         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5446             SendToProgram("go\n", cps); 
5447         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5448     }
5449     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5450 }
5451
5452 char *savedMessage;
5453 ChessProgramState *savedState;
5454 void DeferredBookMove(void)
5455 {
5456         if(savedState->lastPing != savedState->lastPong)
5457                     ScheduleDelayedEvent(DeferredBookMove, 10);
5458         else
5459         HandleMachineMove(savedMessage, savedState);
5460 }
5461
5462 void
5463 HandleMachineMove(message, cps)
5464      char *message;
5465      ChessProgramState *cps;
5466 {
5467     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5468     char realname[MSG_SIZ];
5469     int fromX, fromY, toX, toY;
5470     ChessMove moveType;
5471     char promoChar;
5472     char *p;
5473     int machineWhite;
5474     char *bookHit;
5475
5476 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5477     /*
5478      * Kludge to ignore BEL characters
5479      */
5480     while (*message == '\007') message++;
5481
5482     /*
5483      * [HGM] engine debug message: ignore lines starting with '#' character
5484      */
5485     if(cps->debug && *message == '#') return;
5486
5487     /*
5488      * Look for book output
5489      */
5490     if (cps == &first && bookRequested) {
5491         if (message[0] == '\t' || message[0] == ' ') {
5492             /* Part of the book output is here; append it */
5493             strcat(bookOutput, message);
5494             strcat(bookOutput, "  \n");
5495             return;
5496         } else if (bookOutput[0] != NULLCHAR) {
5497             /* All of book output has arrived; display it */
5498             char *p = bookOutput;
5499             while (*p != NULLCHAR) {
5500                 if (*p == '\t') *p = ' ';
5501                 p++;
5502             }
5503             DisplayInformation(bookOutput);
5504             bookRequested = FALSE;
5505             /* Fall through to parse the current output */
5506         }
5507     }
5508
5509     /*
5510      * Look for machine move.
5511      */
5512     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5513         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5514     {
5515         /* This method is only useful on engines that support ping */
5516         if (cps->lastPing != cps->lastPong) {
5517           if (gameMode == BeginningOfGame) {
5518             /* Extra move from before last new; ignore */
5519             if (appData.debugMode) {
5520                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5521             }
5522           } else {
5523             if (appData.debugMode) {
5524                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5525                         cps->which, gameMode);
5526             }
5527
5528             SendToProgram("undo\n", cps);
5529           }
5530           return;
5531         }
5532
5533         switch (gameMode) {
5534           case BeginningOfGame:
5535             /* Extra move from before last reset; ignore */
5536             if (appData.debugMode) {
5537                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5538             }
5539             return;
5540
5541           case EndOfGame:
5542           case IcsIdle:
5543           default:
5544             /* Extra move after we tried to stop.  The mode test is
5545                not a reliable way of detecting this problem, but it's
5546                the best we can do on engines that don't support ping.
5547             */
5548             if (appData.debugMode) {
5549                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5550                         cps->which, gameMode);
5551             }
5552             SendToProgram("undo\n", cps);
5553             return;
5554
5555           case MachinePlaysWhite:
5556           case IcsPlayingWhite:
5557             machineWhite = TRUE;
5558             break;
5559
5560           case MachinePlaysBlack:
5561           case IcsPlayingBlack:
5562             machineWhite = FALSE;
5563             break;
5564
5565           case TwoMachinesPlay:
5566             machineWhite = (cps->twoMachinesColor[0] == 'w');
5567             break;
5568         }
5569         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5570             if (appData.debugMode) {
5571                 fprintf(debugFP,
5572                         "Ignoring move out of turn by %s, gameMode %d"
5573                         ", forwardMost %d\n",
5574                         cps->which, gameMode, forwardMostMove);
5575             }
5576             return;
5577         }
5578
5579     if (appData.debugMode) { int f = forwardMostMove;
5580         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5581                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5582     }
5583         if(cps->alphaRank) AlphaRank(machineMove, 4);
5584         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5585                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5586             /* Machine move could not be parsed; ignore it. */
5587             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5588                     machineMove, cps->which);
5589             DisplayError(buf1, 0);
5590             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5591                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5592             if (gameMode == TwoMachinesPlay) {
5593               GameEnds(machineWhite ? BlackWins : WhiteWins,
5594                        buf1, GE_XBOARD);
5595             }
5596             return;
5597         }
5598
5599         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5600         /* So we have to redo legality test with true e.p. status here,  */
5601         /* to make sure an illegal e.p. capture does not slip through,   */
5602         /* to cause a forfeit on a justified illegal-move complaint      */
5603         /* of the opponent.                                              */
5604         if( gameMode==TwoMachinesPlay && appData.testLegality
5605             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5606                                                               ) {
5607            ChessMove moveType;
5608            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5609                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5610                              fromY, fromX, toY, toX, promoChar);
5611             if (appData.debugMode) {
5612                 int i;
5613                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5614                     castlingRights[forwardMostMove][i], castlingRank[i]);
5615                 fprintf(debugFP, "castling rights\n");
5616             }
5617             if(moveType == IllegalMove) {
5618                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5619                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5620                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5621                            buf1, GE_XBOARD);
5622                 return;
5623            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5624            /* [HGM] Kludge to handle engines that send FRC-style castling
5625               when they shouldn't (like TSCP-Gothic) */
5626            switch(moveType) {
5627              case WhiteASideCastleFR:
5628              case BlackASideCastleFR:
5629                toX+=2;
5630                currentMoveString[2]++;
5631                break;
5632              case WhiteHSideCastleFR:
5633              case BlackHSideCastleFR:
5634                toX--;
5635                currentMoveString[2]--;
5636                break;
5637              default: ; // nothing to do, but suppresses warning of pedantic compilers
5638            }
5639         }
5640         hintRequested = FALSE;
5641         lastHint[0] = NULLCHAR;
5642         bookRequested = FALSE;
5643         /* Program may be pondering now */
5644         cps->maybeThinking = TRUE;
5645         if (cps->sendTime == 2) cps->sendTime = 1;
5646         if (cps->offeredDraw) cps->offeredDraw--;
5647
5648 #if ZIPPY
5649         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5650             first.initDone) {
5651           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5652           ics_user_moved = 1;
5653           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5654                 char buf[3*MSG_SIZ];
5655
5656                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5657                         programStats.score / 100.,
5658                         programStats.depth,
5659                         programStats.time / 100.,
5660                         (unsigned int)programStats.nodes,
5661                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5662                         programStats.movelist);
5663                 SendToICS(buf);
5664 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5665           }
5666         }
5667 #endif
5668         /* currentMoveString is set as a side-effect of ParseOneMove */
5669         strcpy(machineMove, currentMoveString);
5670         strcat(machineMove, "\n");
5671         strcpy(moveList[forwardMostMove], machineMove);
5672
5673         /* [AS] Save move info and clear stats for next move */
5674         pvInfoList[ forwardMostMove ].score = programStats.score;
5675         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5676         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5677         ClearProgramStats();
5678         thinkOutput[0] = NULLCHAR;
5679         hiddenThinkOutputState = 0;
5680
5681         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5682
5683         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5684         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5685             int count = 0;
5686
5687             while( count < adjudicateLossPlies ) {
5688                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5689
5690                 if( count & 1 ) {
5691                     score = -score; /* Flip score for winning side */
5692                 }
5693
5694                 if( score > adjudicateLossThreshold ) {
5695                     break;
5696                 }
5697
5698                 count++;
5699             }
5700
5701             if( count >= adjudicateLossPlies ) {
5702                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5703
5704                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5705                     "Xboard adjudication", 
5706                     GE_XBOARD );
5707
5708                 return;
5709             }
5710         }
5711
5712         if( gameMode == TwoMachinesPlay ) {
5713           // [HGM] some adjudications useful with buggy engines
5714             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5715           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5716
5717
5718             if( appData.testLegality )
5719             {   /* [HGM] Some more adjudications for obstinate engines */
5720                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5721                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5722                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5723                 static int moveCount = 6;
5724                 ChessMove result;
5725                 char *reason = NULL;
5726
5727                 /* Count what is on board. */
5728                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5729                 {   ChessSquare p = boards[forwardMostMove][i][j];
5730                     int m=i;
5731
5732                     switch((int) p)
5733                     {   /* count B,N,R and other of each side */
5734                         case WhiteKing:
5735                         case BlackKing:
5736                              NrK++; break; // [HGM] atomic: count Kings
5737                         case WhiteKnight:
5738                              NrWN++; break;
5739                         case WhiteBishop:
5740                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5741                              bishopsColor |= 1 << ((i^j)&1);
5742                              NrWB++; break;
5743                         case BlackKnight:
5744                              NrBN++; break;
5745                         case BlackBishop:
5746                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5747                              bishopsColor |= 1 << ((i^j)&1);
5748                              NrBB++; break;
5749                         case WhiteRook:
5750                              NrWR++; break;
5751                         case BlackRook:
5752                              NrBR++; break;
5753                         case WhiteQueen:
5754                              NrWQ++; break;
5755                         case BlackQueen:
5756                              NrBQ++; break;
5757                         case EmptySquare: 
5758                              break;
5759                         case BlackPawn:
5760                              m = 7-i;
5761                         case WhitePawn:
5762                              PawnAdvance += m; NrPawns++;
5763                     }
5764                     NrPieces += (p != EmptySquare);
5765                     NrW += ((int)p < (int)BlackPawn);
5766                     if(gameInfo.variant == VariantXiangqi && 
5767                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5768                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5769                         NrW -= ((int)p < (int)BlackPawn);
5770                     }
5771                 }
5772
5773                 /* Some material-based adjudications that have to be made before stalemate test */
5774                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5775                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5776                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5777                      if(appData.checkMates) {
5778                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5779                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5780                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5781                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5782                          return;
5783                      }
5784                 }
5785
5786                 /* Bare King in Shatranj (loses) or Losers (wins) */
5787                 if( NrW == 1 || NrPieces - NrW == 1) {
5788                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5789                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5790                      if(appData.checkMates) {
5791                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5792                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5793                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5794                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5795                          return;
5796                      }
5797                   } else
5798                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5799                   {    /* bare King */
5800                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5801                         if(appData.checkMates) {
5802                             /* but only adjudicate if adjudication enabled */
5803                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5804                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5805                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5806                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5807                             return;
5808                         }
5809                   }
5810                 } else bare = 1;
5811
5812
5813             // don't wait for engine to announce game end if we can judge ourselves
5814             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5815                                        castlingRights[forwardMostMove]) ) {
5816               case MT_CHECK:
5817                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5818                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5819                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5820                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5821                             checkCnt++;
5822                         if(checkCnt >= 2) {
5823                             reason = "Xboard adjudication: 3rd check";
5824                             epStatus[forwardMostMove] = EP_CHECKMATE;
5825                             break;
5826                         }
5827                     }
5828                 }
5829               case MT_NONE:
5830               default:
5831                 break;
5832               case MT_STALEMATE:
5833               case MT_STAINMATE:
5834                 reason = "Xboard adjudication: Stalemate";
5835                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5836                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5837                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5838                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5839                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5840                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5841                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5842                                                                         EP_CHECKMATE : EP_WINS);
5843                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5844                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5845                 }
5846                 break;
5847               case MT_CHECKMATE:
5848                 reason = "Xboard adjudication: Checkmate";
5849                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5850                 break;
5851             }
5852
5853                 switch(i = epStatus[forwardMostMove]) {
5854                     case EP_STALEMATE:
5855                         result = GameIsDrawn; break;
5856                     case EP_CHECKMATE:
5857                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5858                     case EP_WINS:
5859                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5860                     default:
5861                         result = (ChessMove) 0;
5862                 }
5863                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5864                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5865                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5866                     GameEnds( result, reason, GE_XBOARD );
5867                     return;
5868                 }
5869
5870                 /* Next absolutely insufficient mating material. */
5871                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5872                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5873                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5874                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5875                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5876
5877                      /* always flag draws, for judging claims */
5878                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5879
5880                      if(appData.materialDraws) {
5881                          /* but only adjudicate them if adjudication enabled */
5882                          SendToProgram("force\n", cps->other); // suppress reply
5883                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5884                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5885                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5886                          return;
5887                      }
5888                 }
5889
5890                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5891                 if(NrPieces == 4 && 
5892                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5893                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5894                    || NrWN==2 || NrBN==2     /* KNNK */
5895                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5896                   ) ) {
5897                      if(--moveCount < 0 && appData.trivialDraws)
5898                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5899                           SendToProgram("force\n", cps->other); // suppress reply
5900                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5901                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5902                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5903                           return;
5904                      }
5905                 } else moveCount = 6;
5906             }
5907           }
5908           
5909           if (appData.debugMode) { int i;
5910             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5911                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5912                     appData.drawRepeats);
5913             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5914               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5915             
5916           }
5917
5918                 /* Check for rep-draws */
5919                 count = 0;
5920                 for(k = forwardMostMove-2;
5921                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5922                         epStatus[k] < EP_UNKNOWN &&
5923                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5924                     k-=2)
5925                 {   int rights=0;
5926                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5927                         /* compare castling rights */
5928                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5929                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5930                                 rights++; /* King lost rights, while rook still had them */
5931                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5932                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5933                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5934                                    rights++; /* but at least one rook lost them */
5935                         }
5936                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5937                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5938                                 rights++; 
5939                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5940                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5941                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5942                                    rights++;
5943                         }
5944                         if( rights == 0 && ++count > appData.drawRepeats-2
5945                             && appData.drawRepeats > 1) {
5946                              /* adjudicate after user-specified nr of repeats */
5947                              SendToProgram("force\n", cps->other); // suppress reply
5948                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5949                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5950                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5951                                 // [HGM] xiangqi: check for forbidden perpetuals
5952                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5953                                 for(m=forwardMostMove; m>k; m-=2) {
5954                                     if(MateTest(boards[m], PosFlags(m), 
5955                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5956                                         ourPerpetual = 0; // the current mover did not always check
5957                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5958                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5959                                         hisPerpetual = 0; // the opponent did not always check
5960                                 }
5961                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5962                                                                         ourPerpetual, hisPerpetual);
5963                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5964                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5965                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5966                                     return;
5967                                 }
5968                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5969                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5970                                 // Now check for perpetual chases
5971                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5972                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5973                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5974                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5975                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5976                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5977                                         return;
5978                                     }
5979                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5980                                         break; // Abort repetition-checking loop.
5981                                 }
5982                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5983                              }
5984                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5985                              return;
5986                         }
5987                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5988                              epStatus[forwardMostMove] = EP_REP_DRAW;
5989                     }
5990                 }
5991
5992                 /* Now we test for 50-move draws. Determine ply count */
5993                 count = forwardMostMove;
5994                 /* look for last irreversble move */
5995                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5996                     count--;
5997                 /* if we hit starting position, add initial plies */
5998                 if( count == backwardMostMove )
5999                     count -= initialRulePlies;
6000                 count = forwardMostMove - count; 
6001                 if( count >= 100)
6002                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6003                          /* this is used to judge if draw claims are legal */
6004                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6005                          SendToProgram("force\n", cps->other); // suppress reply
6006                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6007                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6008                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6009                          return;
6010                 }
6011
6012                 /* if draw offer is pending, treat it as a draw claim
6013                  * when draw condition present, to allow engines a way to
6014                  * claim draws before making their move to avoid a race
6015                  * condition occurring after their move
6016                  */
6017                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6018                          char *p = NULL;
6019                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6020                              p = "Draw claim: 50-move rule";
6021                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6022                              p = "Draw claim: 3-fold repetition";
6023                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6024                              p = "Draw claim: insufficient mating material";
6025                          if( p != NULL ) {
6026                              SendToProgram("force\n", cps->other); // suppress reply
6027                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6028                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6029                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6030                              return;
6031                          }
6032                 }
6033
6034
6035                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6036                     SendToProgram("force\n", cps->other); // suppress reply
6037                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6038                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6039
6040                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6041
6042                     return;
6043                 }
6044         }
6045
6046         bookHit = NULL;
6047         if (gameMode == TwoMachinesPlay) {
6048             /* [HGM] relaying draw offers moved to after reception of move */
6049             /* and interpreting offer as claim if it brings draw condition */
6050             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6051                 SendToProgram("draw\n", cps->other);
6052             }
6053             if (cps->other->sendTime) {
6054                 SendTimeRemaining(cps->other,
6055                                   cps->other->twoMachinesColor[0] == 'w');
6056             }
6057             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6058             if (firstMove && !bookHit) {
6059                 firstMove = FALSE;
6060                 if (cps->other->useColors) {
6061                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6062                 }
6063                 SendToProgram("go\n", cps->other);
6064             }
6065             cps->other->maybeThinking = TRUE;
6066         }
6067
6068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069         
6070         if (!pausing && appData.ringBellAfterMoves) {
6071             RingBell();
6072         }
6073
6074         /* 
6075          * Reenable menu items that were disabled while
6076          * machine was thinking
6077          */
6078         if (gameMode != TwoMachinesPlay)
6079             SetUserThinkingEnables();
6080
6081         // [HGM] book: after book hit opponent has received move and is now in force mode
6082         // force the book reply into it, and then fake that it outputted this move by jumping
6083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6084         if(bookHit) {
6085                 static char bookMove[MSG_SIZ]; // a bit generous?
6086
6087                 strcpy(bookMove, "move ");
6088                 strcat(bookMove, bookHit);
6089                 message = bookMove;
6090                 cps = cps->other;
6091                 programStats.nodes = programStats.depth = programStats.time = 
6092                 programStats.score = programStats.got_only_move = 0;
6093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6094
6095                 if(cps->lastPing != cps->lastPong) {
6096                     savedMessage = message; // args for deferred call
6097                     savedState = cps;
6098                     ScheduleDelayedEvent(DeferredBookMove, 10);
6099                     return;
6100                 }
6101                 goto FakeBookMove;
6102         }
6103
6104         return;
6105     }
6106
6107     /* Set special modes for chess engines.  Later something general
6108      *  could be added here; for now there is just one kludge feature,
6109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6110      *  when "xboard" is given as an interactive command.
6111      */
6112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6113         cps->useSigint = FALSE;
6114         cps->useSigterm = FALSE;
6115     }
6116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6117       ParseFeatures(message+8, cps);
6118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6119     }
6120
6121     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6122      * want this, I was asked to put it in, and obliged.
6123      */
6124     if (!strncmp(message, "setboard ", 9)) {
6125         Board initial_position; int i;
6126
6127         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6128
6129         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6130             DisplayError(_("Bad FEN received from engine"), 0);
6131             return ;
6132         } else {
6133            Reset(FALSE, FALSE);
6134            CopyBoard(boards[0], initial_position);
6135            initialRulePlies = FENrulePlies;
6136            epStatus[0] = FENepStatus;
6137            for( i=0; i<nrCastlingRights; i++ )
6138                 castlingRights[0][i] = FENcastlingRights[i];
6139            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6140            else gameMode = MachinePlaysBlack;                 
6141            DrawPosition(FALSE, boards[currentMove]);
6142         }
6143         return;
6144     }
6145
6146     /*
6147      * Look for communication commands
6148      */
6149     if (!strncmp(message, "telluser ", 9)) {
6150         DisplayNote(message + 9);
6151         return;
6152     }
6153     if (!strncmp(message, "tellusererror ", 14)) {
6154         DisplayError(message + 14, 0);
6155         return;
6156     }
6157     if (!strncmp(message, "tellopponent ", 13)) {
6158       if (appData.icsActive) {
6159         if (loggedOn) {
6160           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6161           SendToICS(buf1);
6162         }
6163       } else {
6164         DisplayNote(message + 13);
6165       }
6166       return;
6167     }
6168     if (!strncmp(message, "tellothers ", 11)) {
6169       if (appData.icsActive) {
6170         if (loggedOn) {
6171           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6172           SendToICS(buf1);
6173         }
6174       }
6175       return;
6176     }
6177     if (!strncmp(message, "tellall ", 8)) {
6178       if (appData.icsActive) {
6179         if (loggedOn) {
6180           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6181           SendToICS(buf1);
6182         }
6183       } else {
6184         DisplayNote(message + 8);
6185       }
6186       return;
6187     }
6188     if (strncmp(message, "warning", 7) == 0) {
6189         /* Undocumented feature, use tellusererror in new code */
6190         DisplayError(message, 0);
6191         return;
6192     }
6193     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6194         strcpy(realname, cps->tidy);
6195         strcat(realname, " query");
6196         AskQuestion(realname, buf2, buf1, cps->pr);
6197         return;
6198     }
6199     /* Commands from the engine directly to ICS.  We don't allow these to be 
6200      *  sent until we are logged on. Crafty kibitzes have been known to 
6201      *  interfere with the login process.
6202      */
6203     if (loggedOn) {
6204         if (!strncmp(message, "tellics ", 8)) {
6205             SendToICS(message + 8);
6206             SendToICS("\n");
6207             return;
6208         }
6209         if (!strncmp(message, "tellicsnoalias ", 15)) {
6210             SendToICS(ics_prefix);
6211             SendToICS(message + 15);
6212             SendToICS("\n");
6213             return;
6214         }
6215         /* The following are for backward compatibility only */
6216         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6217             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6218             SendToICS(ics_prefix);
6219             SendToICS(message);
6220             SendToICS("\n");
6221             return;
6222         }
6223     }
6224     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6225         return;
6226     }
6227     /*
6228      * If the move is illegal, cancel it and redraw the board.
6229      * Also deal with other error cases.  Matching is rather loose
6230      * here to accommodate engines written before the spec.
6231      */
6232     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6233         strncmp(message, "Error", 5) == 0) {
6234         if (StrStr(message, "name") || 
6235             StrStr(message, "rating") || StrStr(message, "?") ||
6236             StrStr(message, "result") || StrStr(message, "board") ||
6237             StrStr(message, "bk") || StrStr(message, "computer") ||
6238             StrStr(message, "variant") || StrStr(message, "hint") ||
6239             StrStr(message, "random") || StrStr(message, "depth") ||
6240             StrStr(message, "accepted")) {
6241             return;
6242         }
6243         if (StrStr(message, "protover")) {
6244           /* Program is responding to input, so it's apparently done
6245              initializing, and this error message indicates it is
6246              protocol version 1.  So we don't need to wait any longer
6247              for it to initialize and send feature commands. */
6248           FeatureDone(cps, 1);
6249           cps->protocolVersion = 1;
6250           return;
6251         }
6252         cps->maybeThinking = FALSE;
6253
6254         if (StrStr(message, "draw")) {
6255             /* Program doesn't have "draw" command */
6256             cps->sendDrawOffers = 0;
6257             return;
6258         }
6259         if (cps->sendTime != 1 &&
6260             (StrStr(message, "time") || StrStr(message, "otim"))) {
6261           /* Program apparently doesn't have "time" or "otim" command */
6262           cps->sendTime = 0;
6263           return;
6264         }
6265         if (StrStr(message, "analyze")) {
6266             cps->analysisSupport = FALSE;
6267             cps->analyzing = FALSE;
6268             Reset(FALSE, TRUE);
6269             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6270             DisplayError(buf2, 0);
6271             return;
6272         }
6273         if (StrStr(message, "(no matching move)st")) {
6274           /* Special kludge for GNU Chess 4 only */
6275           cps->stKludge = TRUE;
6276           SendTimeControl(cps, movesPerSession, timeControl,
6277                           timeIncrement, appData.searchDepth,
6278                           searchTime);
6279           return;
6280         }
6281         if (StrStr(message, "(no matching move)sd")) {
6282           /* Special kludge for GNU Chess 4 only */
6283           cps->sdKludge = TRUE;
6284           SendTimeControl(cps, movesPerSession, timeControl,
6285                           timeIncrement, appData.searchDepth,
6286                           searchTime);
6287           return;
6288         }
6289         if (!StrStr(message, "llegal")) {
6290             return;
6291         }
6292         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6293             gameMode == IcsIdle) return;
6294         if (forwardMostMove <= backwardMostMove) return;
6295         if (pausing) PauseEvent();
6296       if(appData.forceIllegal) {
6297             // [HGM] illegal: machine refused move; force position after move into it
6298           SendToProgram("force\n", cps);
6299           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6300                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6301                 // when black is to move, while there might be nothing on a2 or black
6302                 // might already have the move. So send the board as if white has the move.
6303                 // But first we must change the stm of the engine, as it refused the last move
6304                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6305                 if(WhiteOnMove(forwardMostMove)) {
6306                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6307                     SendBoard(cps, forwardMostMove); // kludgeless board
6308                 } else {
6309                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6310                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6311                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6312                 }
6313           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6314             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6315                  gameMode == TwoMachinesPlay)
6316               SendToProgram("go\n", cps);
6317             return;
6318       } else
6319         if (gameMode == PlayFromGameFile) {
6320             /* Stop reading this game file */
6321             gameMode = EditGame;
6322             ModeHighlight();
6323         }
6324         currentMove = --forwardMostMove;
6325         DisplayMove(currentMove-1); /* before DisplayMoveError */
6326         SwitchClocks();
6327         DisplayBothClocks();
6328         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6329                 parseList[currentMove], cps->which);
6330         DisplayMoveError(buf1);
6331         DrawPosition(FALSE, boards[currentMove]);
6332
6333         /* [HGM] illegal-move claim should forfeit game when Xboard */
6334         /* only passes fully legal moves                            */
6335         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6336             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6337                                 "False illegal-move claim", GE_XBOARD );
6338         }
6339         return;
6340     }
6341     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6342         /* Program has a broken "time" command that
6343            outputs a string not ending in newline.
6344            Don't use it. */
6345         cps->sendTime = 0;
6346     }
6347     
6348     /*
6349      * If chess program startup fails, exit with an error message.
6350      * Attempts to recover here are futile.
6351      */
6352     if ((StrStr(message, "unknown host") != NULL)
6353         || (StrStr(message, "No remote directory") != NULL)
6354         || (StrStr(message, "not found") != NULL)
6355         || (StrStr(message, "No such file") != NULL)
6356         || (StrStr(message, "can't alloc") != NULL)
6357         || (StrStr(message, "Permission denied") != NULL)) {
6358
6359         cps->maybeThinking = FALSE;
6360         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6361                 cps->which, cps->program, cps->host, message);
6362         RemoveInputSource(cps->isr);
6363         DisplayFatalError(buf1, 0, 1);
6364         return;
6365     }
6366     
6367     /* 
6368      * Look for hint output
6369      */
6370     if (sscanf(message, "Hint: %s", buf1) == 1) {
6371         if (cps == &first && hintRequested) {
6372             hintRequested = FALSE;
6373             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6374                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6375                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6376                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6377                                     fromY, fromX, toY, toX, promoChar, buf1);
6378                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6379                 DisplayInformation(buf2);
6380             } else {
6381                 /* Hint move could not be parsed!? */
6382               snprintf(buf2, sizeof(buf2),
6383                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6384                         buf1, cps->which);
6385                 DisplayError(buf2, 0);
6386             }
6387         } else {
6388             strcpy(lastHint, buf1);
6389         }
6390         return;
6391     }
6392
6393     /*
6394      * Ignore other messages if game is not in progress
6395      */
6396     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6397         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6398
6399     /*
6400      * look for win, lose, draw, or draw offer
6401      */
6402     if (strncmp(message, "1-0", 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         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6413         return;
6414     } else if (strncmp(message, "0-1", 3) == 0) {
6415         char *p, *q, *r = "";
6416         p = strchr(message, '{');
6417         if (p) {
6418             q = strchr(p, '}');
6419             if (q) {
6420                 *q = NULLCHAR;
6421                 r = p + 1;
6422             }
6423         }
6424         /* Kludge for Arasan 4.1 bug */
6425         if (strcmp(r, "Black resigns") == 0) {
6426             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6427             return;
6428         }
6429         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6430         return;
6431     } else if (strncmp(message, "1/2", 3) == 0) {
6432         char *p, *q, *r = "";
6433         p = strchr(message, '{');
6434         if (p) {
6435             q = strchr(p, '}');
6436             if (q) {
6437                 *q = NULLCHAR;
6438                 r = p + 1;
6439             }
6440         }
6441             
6442         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6443         return;
6444
6445     } else if (strncmp(message, "White resign", 12) == 0) {
6446         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6447         return;
6448     } else if (strncmp(message, "Black resign", 12) == 0) {
6449         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6450         return;
6451     } else if (strncmp(message, "White matches", 13) == 0 ||
6452                strncmp(message, "Black matches", 13) == 0   ) {
6453         /* [HGM] ignore GNUShogi noises */
6454         return;
6455     } else if (strncmp(message, "White", 5) == 0 &&
6456                message[5] != '(' &&
6457                StrStr(message, "Black") == NULL) {
6458         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6459         return;
6460     } else if (strncmp(message, "Black", 5) == 0 &&
6461                message[5] != '(') {
6462         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6463         return;
6464     } else if (strcmp(message, "resign") == 0 ||
6465                strcmp(message, "computer resigns") == 0) {
6466         switch (gameMode) {
6467           case MachinePlaysBlack:
6468           case IcsPlayingBlack:
6469             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6470             break;
6471           case MachinePlaysWhite:
6472           case IcsPlayingWhite:
6473             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6474             break;
6475           case TwoMachinesPlay:
6476             if (cps->twoMachinesColor[0] == 'w')
6477               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6478             else
6479               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6480             break;
6481           default:
6482             /* can't happen */
6483             break;
6484         }
6485         return;
6486     } else if (strncmp(message, "opponent mates", 14) == 0) {
6487         switch (gameMode) {
6488           case MachinePlaysBlack:
6489           case IcsPlayingBlack:
6490             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6491             break;
6492           case MachinePlaysWhite:
6493           case IcsPlayingWhite:
6494             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6495             break;
6496           case TwoMachinesPlay:
6497             if (cps->twoMachinesColor[0] == 'w')
6498               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6499             else
6500               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6501             break;
6502           default:
6503             /* can't happen */
6504             break;
6505         }
6506         return;
6507     } else if (strncmp(message, "computer mates", 14) == 0) {
6508         switch (gameMode) {
6509           case MachinePlaysBlack:
6510           case IcsPlayingBlack:
6511             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6512             break;
6513           case MachinePlaysWhite:
6514           case IcsPlayingWhite:
6515             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6516             break;
6517           case TwoMachinesPlay:
6518             if (cps->twoMachinesColor[0] == 'w')
6519               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520             else
6521               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6522             break;
6523           default:
6524             /* can't happen */
6525             break;
6526         }
6527         return;
6528     } else if (strncmp(message, "checkmate", 9) == 0) {
6529         if (WhiteOnMove(forwardMostMove)) {
6530             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6531         } else {
6532             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6533         }
6534         return;
6535     } else if (strstr(message, "Draw") != NULL ||
6536                strstr(message, "game is a draw") != NULL) {
6537         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6538         return;
6539     } else if (strstr(message, "offer") != NULL &&
6540                strstr(message, "draw") != NULL) {
6541 #if ZIPPY
6542         if (appData.zippyPlay && first.initDone) {
6543             /* Relay offer to ICS */
6544             SendToICS(ics_prefix);
6545             SendToICS("draw\n");
6546         }
6547 #endif
6548         cps->offeredDraw = 2; /* valid until this engine moves twice */
6549         if (gameMode == TwoMachinesPlay) {
6550             if (cps->other->offeredDraw) {
6551                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6552             /* [HGM] in two-machine mode we delay relaying draw offer      */
6553             /* until after we also have move, to see if it is really claim */
6554             }
6555         } else if (gameMode == MachinePlaysWhite ||
6556                    gameMode == MachinePlaysBlack) {
6557           if (userOfferedDraw) {
6558             DisplayInformation(_("Machine accepts your draw offer"));
6559             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6560           } else {
6561             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6562           }
6563         }
6564     }
6565
6566     
6567     /*
6568      * Look for thinking output
6569      */
6570     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6571           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6572                                 ) {
6573         int plylev, mvleft, mvtot, curscore, time;
6574         char mvname[MOVE_LEN];
6575         u64 nodes; // [DM]
6576         char plyext;
6577         int ignore = FALSE;
6578         int prefixHint = FALSE;
6579         mvname[0] = NULLCHAR;
6580
6581         switch (gameMode) {
6582           case MachinePlaysBlack:
6583           case IcsPlayingBlack:
6584             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6585             break;
6586           case MachinePlaysWhite:
6587           case IcsPlayingWhite:
6588             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6589             break;
6590           case AnalyzeMode:
6591           case AnalyzeFile:
6592             break;
6593           case IcsObserving: /* [DM] icsEngineAnalyze */
6594             if (!appData.icsEngineAnalyze) ignore = TRUE;
6595             break;
6596           case TwoMachinesPlay:
6597             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6598                 ignore = TRUE;
6599             }
6600             break;
6601           default:
6602             ignore = TRUE;
6603             break;
6604         }
6605
6606         if (!ignore) {
6607             buf1[0] = NULLCHAR;
6608             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6609                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6610
6611                 if (plyext != ' ' && plyext != '\t') {
6612                     time *= 100;
6613                 }
6614
6615                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6616                 if( cps->scoreIsAbsolute && 
6617                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6618                 {
6619                     curscore = -curscore;
6620                 }
6621
6622
6623                 programStats.depth = plylev;
6624                 programStats.nodes = nodes;
6625                 programStats.time = time;
6626                 programStats.score = curscore;
6627                 programStats.got_only_move = 0;
6628
6629                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6630                         int ticklen;
6631
6632                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6633                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6634                         if(WhiteOnMove(forwardMostMove)) 
6635                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6636                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6637                 }
6638
6639                 /* Buffer overflow protection */
6640                 if (buf1[0] != NULLCHAR) {
6641                     if (strlen(buf1) >= sizeof(programStats.movelist)
6642                         && appData.debugMode) {
6643                         fprintf(debugFP,
6644                                 "PV is too long; using the first %d bytes.\n",
6645                                 sizeof(programStats.movelist) - 1);
6646                     }
6647
6648                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6649                 } else {
6650                     sprintf(programStats.movelist, " no PV\n");
6651                 }
6652
6653                 if (programStats.seen_stat) {
6654                     programStats.ok_to_send = 1;
6655                 }
6656
6657                 if (strchr(programStats.movelist, '(') != NULL) {
6658                     programStats.line_is_book = 1;
6659                     programStats.nr_moves = 0;
6660                     programStats.moves_left = 0;
6661                 } else {
6662                     programStats.line_is_book = 0;
6663                 }
6664
6665                 SendProgramStatsToFrontend( cps, &programStats );
6666
6667                 /* 
6668                     [AS] Protect the thinkOutput buffer from overflow... this
6669                     is only useful if buf1 hasn't overflowed first!
6670                 */
6671                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6672                         plylev, 
6673                         (gameMode == TwoMachinesPlay ?
6674                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6675                         ((double) curscore) / 100.0,
6676                         prefixHint ? lastHint : "",
6677                         prefixHint ? " " : "" );
6678
6679                 if( buf1[0] != NULLCHAR ) {
6680                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6681
6682                     if( strlen(buf1) > max_len ) {
6683                         if( appData.debugMode) {
6684                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6685                         }
6686                         buf1[max_len+1] = '\0';
6687                     }
6688
6689                     strcat( thinkOutput, buf1 );
6690                 }
6691
6692                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6693                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6694                     DisplayMove(currentMove - 1);
6695                     DisplayAnalysis();
6696                 }
6697                 return;
6698
6699             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6700                 /* crafty (9.25+) says "(only move) <move>"
6701                  * if there is only 1 legal move
6702                  */
6703                 sscanf(p, "(only move) %s", buf1);
6704                 sprintf(thinkOutput, "%s (only move)", buf1);
6705                 sprintf(programStats.movelist, "%s (only move)", buf1);
6706                 programStats.depth = 1;
6707                 programStats.nr_moves = 1;
6708                 programStats.moves_left = 1;
6709                 programStats.nodes = 1;
6710                 programStats.time = 1;
6711                 programStats.got_only_move = 1;
6712
6713                 /* Not really, but we also use this member to
6714                    mean "line isn't going to change" (Crafty
6715                    isn't searching, so stats won't change) */
6716                 programStats.line_is_book = 1;
6717
6718                 SendProgramStatsToFrontend( cps, &programStats );
6719                 
6720                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6721                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6722                     DisplayMove(currentMove - 1);
6723                     DisplayAnalysis();
6724                 }
6725                 return;
6726             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6727                               &time, &nodes, &plylev, &mvleft,
6728                               &mvtot, mvname) >= 5) {
6729                 /* The stat01: line is from Crafty (9.29+) in response
6730                    to the "." command */
6731                 programStats.seen_stat = 1;
6732                 cps->maybeThinking = TRUE;
6733
6734                 if (programStats.got_only_move || !appData.periodicUpdates)
6735                   return;
6736
6737                 programStats.depth = plylev;
6738                 programStats.time = time;
6739                 programStats.nodes = nodes;
6740                 programStats.moves_left = mvleft;
6741                 programStats.nr_moves = mvtot;
6742                 strcpy(programStats.move_name, mvname);
6743                 programStats.ok_to_send = 1;
6744                 programStats.movelist[0] = '\0';
6745
6746                 SendProgramStatsToFrontend( cps, &programStats );
6747
6748                 DisplayAnalysis();
6749                 return;
6750
6751             } else if (strncmp(message,"++",2) == 0) {
6752                 /* Crafty 9.29+ outputs this */
6753                 programStats.got_fail = 2;
6754                 return;
6755
6756             } else if (strncmp(message,"--",2) == 0) {
6757                 /* Crafty 9.29+ outputs this */
6758                 programStats.got_fail = 1;
6759                 return;
6760
6761             } else if (thinkOutput[0] != NULLCHAR &&
6762                        strncmp(message, "    ", 4) == 0) {
6763                 unsigned message_len;
6764
6765                 p = message;
6766                 while (*p && *p == ' ') p++;
6767
6768                 message_len = strlen( p );
6769
6770                 /* [AS] Avoid buffer overflow */
6771                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6772                     strcat(thinkOutput, " ");
6773                     strcat(thinkOutput, p);
6774                 }
6775
6776                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6777                     strcat(programStats.movelist, " ");
6778                     strcat(programStats.movelist, p);
6779                 }
6780
6781                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6782                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6783                     DisplayMove(currentMove - 1);
6784                     DisplayAnalysis();
6785                 }
6786                 return;
6787             }
6788         }
6789         else {
6790             buf1[0] = NULLCHAR;
6791
6792             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6793                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6794             {
6795                 ChessProgramStats cpstats;
6796
6797                 if (plyext != ' ' && plyext != '\t') {
6798                     time *= 100;
6799                 }
6800
6801                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6802                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6803                     curscore = -curscore;
6804                 }
6805
6806                 cpstats.depth = plylev;
6807                 cpstats.nodes = nodes;
6808                 cpstats.time = time;
6809                 cpstats.score = curscore;
6810                 cpstats.got_only_move = 0;
6811                 cpstats.movelist[0] = '\0';
6812
6813                 if (buf1[0] != NULLCHAR) {
6814                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6815                 }
6816
6817                 cpstats.ok_to_send = 0;
6818                 cpstats.line_is_book = 0;
6819                 cpstats.nr_moves = 0;
6820                 cpstats.moves_left = 0;
6821
6822                 SendProgramStatsToFrontend( cps, &cpstats );
6823             }
6824         }
6825     }
6826 }
6827
6828
6829 /* Parse a game score from the character string "game", and
6830    record it as the history of the current game.  The game
6831    score is NOT assumed to start from the standard position. 
6832    The display is not updated in any way.
6833    */
6834 void
6835 ParseGameHistory(game)
6836      char *game;
6837 {
6838     ChessMove moveType;
6839     int fromX, fromY, toX, toY, boardIndex;
6840     char promoChar;
6841     char *p, *q;
6842     char buf[MSG_SIZ];
6843
6844     if (appData.debugMode)
6845       fprintf(debugFP, "Parsing game history: %s\n", game);
6846
6847     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6848     gameInfo.site = StrSave(appData.icsHost);
6849     gameInfo.date = PGNDate();
6850     gameInfo.round = StrSave("-");
6851
6852     /* Parse out names of players */
6853     while (*game == ' ') game++;
6854     p = buf;
6855     while (*game != ' ') *p++ = *game++;
6856     *p = NULLCHAR;
6857     gameInfo.white = StrSave(buf);
6858     while (*game == ' ') game++;
6859     p = buf;
6860     while (*game != ' ' && *game != '\n') *p++ = *game++;
6861     *p = NULLCHAR;
6862     gameInfo.black = StrSave(buf);
6863
6864     /* Parse moves */
6865     boardIndex = blackPlaysFirst ? 1 : 0;
6866     yynewstr(game);
6867     for (;;) {
6868         yyboardindex = boardIndex;
6869         moveType = (ChessMove) yylex();
6870         switch (moveType) {
6871           case IllegalMove:             /* maybe suicide chess, etc. */
6872   if (appData.debugMode) {
6873     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6874     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6875     setbuf(debugFP, NULL);
6876   }
6877           case WhitePromotionChancellor:
6878           case BlackPromotionChancellor:
6879           case WhitePromotionArchbishop:
6880           case BlackPromotionArchbishop:
6881           case WhitePromotionQueen:
6882           case BlackPromotionQueen:
6883           case WhitePromotionRook:
6884           case BlackPromotionRook:
6885           case WhitePromotionBishop:
6886           case BlackPromotionBishop:
6887           case WhitePromotionKnight:
6888           case BlackPromotionKnight:
6889           case WhitePromotionKing:
6890           case BlackPromotionKing:
6891           case NormalMove:
6892           case WhiteCapturesEnPassant:
6893           case BlackCapturesEnPassant:
6894           case WhiteKingSideCastle:
6895           case WhiteQueenSideCastle:
6896           case BlackKingSideCastle:
6897           case BlackQueenSideCastle:
6898           case WhiteKingSideCastleWild:
6899           case WhiteQueenSideCastleWild:
6900           case BlackKingSideCastleWild:
6901           case BlackQueenSideCastleWild:
6902           /* PUSH Fabien */
6903           case WhiteHSideCastleFR:
6904           case WhiteASideCastleFR:
6905           case BlackHSideCastleFR:
6906           case BlackASideCastleFR:
6907           /* POP Fabien */
6908             fromX = currentMoveString[0] - AAA;
6909             fromY = currentMoveString[1] - ONE;
6910             toX = currentMoveString[2] - AAA;
6911             toY = currentMoveString[3] - ONE;
6912             promoChar = currentMoveString[4];
6913             break;
6914           case WhiteDrop:
6915           case BlackDrop:
6916             fromX = moveType == WhiteDrop ?
6917               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6918             (int) CharToPiece(ToLower(currentMoveString[0]));
6919             fromY = DROP_RANK;
6920             toX = currentMoveString[2] - AAA;
6921             toY = currentMoveString[3] - ONE;
6922             promoChar = NULLCHAR;
6923             break;
6924           case AmbiguousMove:
6925             /* bug? */
6926             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6927   if (appData.debugMode) {
6928     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6929     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6930     setbuf(debugFP, NULL);
6931   }
6932             DisplayError(buf, 0);
6933             return;
6934           case ImpossibleMove:
6935             /* bug? */
6936             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6937   if (appData.debugMode) {
6938     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6939     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6940     setbuf(debugFP, NULL);
6941   }
6942             DisplayError(buf, 0);
6943             return;
6944           case (ChessMove) 0:   /* end of file */
6945             if (boardIndex < backwardMostMove) {
6946                 /* Oops, gap.  How did that happen? */
6947                 DisplayError(_("Gap in move list"), 0);
6948                 return;
6949             }
6950             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6951             if (boardIndex > forwardMostMove) {
6952                 forwardMostMove = boardIndex;
6953             }
6954             return;
6955           case ElapsedTime:
6956             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6957                 strcat(parseList[boardIndex-1], " ");
6958                 strcat(parseList[boardIndex-1], yy_text);
6959             }
6960             continue;
6961           case Comment:
6962           case PGNTag:
6963           case NAG:
6964           default:
6965             /* ignore */
6966             continue;
6967           case WhiteWins:
6968           case BlackWins:
6969           case GameIsDrawn:
6970           case GameUnfinished:
6971             if (gameMode == IcsExamining) {
6972                 if (boardIndex < backwardMostMove) {
6973                     /* Oops, gap.  How did that happen? */
6974                     return;
6975                 }
6976                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6977                 return;
6978             }
6979             gameInfo.result = moveType;
6980             p = strchr(yy_text, '{');
6981             if (p == NULL) p = strchr(yy_text, '(');
6982             if (p == NULL) {
6983                 p = yy_text;
6984                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6985             } else {
6986                 q = strchr(p, *p == '{' ? '}' : ')');
6987                 if (q != NULL) *q = NULLCHAR;
6988                 p++;
6989             }
6990             gameInfo.resultDetails = StrSave(p);
6991             continue;
6992         }
6993         if (boardIndex >= forwardMostMove &&
6994             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6995             backwardMostMove = blackPlaysFirst ? 1 : 0;
6996             return;
6997         }
6998         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6999                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7000                                  parseList[boardIndex]);
7001         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7002         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7003         /* currentMoveString is set as a side-effect of yylex */
7004         strcpy(moveList[boardIndex], currentMoveString);
7005         strcat(moveList[boardIndex], "\n");
7006         boardIndex++;
7007         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7008                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7009         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7010                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7011           case MT_NONE:
7012           case MT_STALEMATE:
7013           default:
7014             break;
7015           case MT_CHECK:
7016             if(gameInfo.variant != VariantShogi)
7017                 strcat(parseList[boardIndex - 1], "+");
7018             break;
7019           case MT_CHECKMATE:
7020           case MT_STAINMATE:
7021             strcat(parseList[boardIndex - 1], "#");
7022             break;
7023         }
7024     }
7025 }
7026
7027
7028 /* Apply a move to the given board  */
7029 void
7030 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7031      int fromX, fromY, toX, toY;
7032      int promoChar;
7033      Board board;
7034      char *castling;
7035      char *ep;
7036 {
7037   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7038
7039     /* [HGM] compute & store e.p. status and castling rights for new position */
7040     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7041     { int i;
7042
7043       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7044       oldEP = *ep;
7045       *ep = EP_NONE;
7046
7047       if( board[toY][toX] != EmptySquare ) 
7048            *ep = EP_CAPTURE;  
7049
7050       if( board[fromY][fromX] == WhitePawn ) {
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] == BlackPawn &&
7055                         gameInfo.variant != VariantBerolina || toX < fromX)
7056                       *ep = toX | berolina;
7057                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7058                         gameInfo.variant != VariantBerolina || toX > fromX) 
7059                       *ep = toX;
7060            }
7061       } else 
7062       if( board[fromY][fromX] == BlackPawn ) {
7063            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7064                *ep = EP_PAWN_MOVE; 
7065            if( toY-fromY== -2) {
7066                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7067                         gameInfo.variant != VariantBerolina || toX < fromX)
7068                       *ep = toX | berolina;
7069                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7070                         gameInfo.variant != VariantBerolina || toX > fromX) 
7071                       *ep = toX;
7072            }
7073        }
7074
7075        for(i=0; i<nrCastlingRights; i++) {
7076            if(castling[i] == fromX && castlingRank[i] == fromY ||
7077               castling[i] == toX   && castlingRank[i] == toY   
7078              ) castling[i] = -1; // revoke for moved or captured piece
7079        }
7080
7081     }
7082
7083   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7084   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7085        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7086          
7087   if (fromX == toX && fromY == toY) return;
7088
7089   if (fromY == DROP_RANK) {
7090         /* must be first */
7091         piece = board[toY][toX] = (ChessSquare) fromX;
7092   } else {
7093      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7094      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7095      if(gameInfo.variant == VariantKnightmate)
7096          king += (int) WhiteUnicorn - (int) WhiteKing;
7097
7098     /* Code added by Tord: */
7099     /* FRC castling assumed when king captures friendly rook. */
7100     if (board[fromY][fromX] == WhiteKing &&
7101              board[toY][toX] == WhiteRook) {
7102       board[fromY][fromX] = EmptySquare;
7103       board[toY][toX] = EmptySquare;
7104       if(toX > fromX) {
7105         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7106       } else {
7107         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7108       }
7109     } else if (board[fromY][fromX] == BlackKing &&
7110                board[toY][toX] == BlackRook) {
7111       board[fromY][fromX] = EmptySquare;
7112       board[toY][toX] = EmptySquare;
7113       if(toX > fromX) {
7114         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7115       } else {
7116         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7117       }
7118     /* End of code added by Tord */
7119
7120     } else if (board[fromY][fromX] == king
7121         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7122         && toY == fromY && toX > fromX+1) {
7123         board[fromY][fromX] = EmptySquare;
7124         board[toY][toX] = king;
7125         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7126         board[fromY][BOARD_RGHT-1] = EmptySquare;
7127     } else if (board[fromY][fromX] == king
7128         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7129                && toY == fromY && toX < fromX-1) {
7130         board[fromY][fromX] = EmptySquare;
7131         board[toY][toX] = king;
7132         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7133         board[fromY][BOARD_LEFT] = EmptySquare;
7134     } else if (board[fromY][fromX] == WhitePawn
7135                && toY == BOARD_HEIGHT-1
7136                && gameInfo.variant != VariantXiangqi
7137                ) {
7138         /* white pawn promotion */
7139         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7140         if (board[toY][toX] == EmptySquare) {
7141             board[toY][toX] = WhiteQueen;
7142         }
7143         if(gameInfo.variant==VariantBughouse ||
7144            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7145             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7146         board[fromY][fromX] = EmptySquare;
7147     } else if ((fromY == BOARD_HEIGHT-4)
7148                && (toX != fromX)
7149                && gameInfo.variant != VariantXiangqi
7150                && gameInfo.variant != VariantBerolina
7151                && (board[fromY][fromX] == WhitePawn)
7152                && (board[toY][toX] == EmptySquare)) {
7153         board[fromY][fromX] = EmptySquare;
7154         board[toY][toX] = WhitePawn;
7155         captured = board[toY - 1][toX];
7156         board[toY - 1][toX] = EmptySquare;
7157     } else if ((fromY == BOARD_HEIGHT-4)
7158                && (toX == fromX)
7159                && gameInfo.variant == VariantBerolina
7160                && (board[fromY][fromX] == WhitePawn)
7161                && (board[toY][toX] == EmptySquare)) {
7162         board[fromY][fromX] = EmptySquare;
7163         board[toY][toX] = WhitePawn;
7164         if(oldEP & EP_BEROLIN_A) {
7165                 captured = board[fromY][fromX-1];
7166                 board[fromY][fromX-1] = EmptySquare;
7167         }else{  captured = board[fromY][fromX+1];
7168                 board[fromY][fromX+1] = EmptySquare;
7169         }
7170     } else if (board[fromY][fromX] == king
7171         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7172                && toY == fromY && toX > fromX+1) {
7173         board[fromY][fromX] = EmptySquare;
7174         board[toY][toX] = king;
7175         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7176         board[fromY][BOARD_RGHT-1] = EmptySquare;
7177     } else if (board[fromY][fromX] == king
7178         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7179                && toY == fromY && toX < fromX-1) {
7180         board[fromY][fromX] = EmptySquare;
7181         board[toY][toX] = king;
7182         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7183         board[fromY][BOARD_LEFT] = EmptySquare;
7184     } else if (fromY == 7 && fromX == 3
7185                && board[fromY][fromX] == BlackKing
7186                && toY == 7 && toX == 5) {
7187         board[fromY][fromX] = EmptySquare;
7188         board[toY][toX] = BlackKing;
7189         board[fromY][7] = EmptySquare;
7190         board[toY][4] = BlackRook;
7191     } else if (fromY == 7 && fromX == 3
7192                && board[fromY][fromX] == BlackKing
7193                && toY == 7 && toX == 1) {
7194         board[fromY][fromX] = EmptySquare;
7195         board[toY][toX] = BlackKing;
7196         board[fromY][0] = EmptySquare;
7197         board[toY][2] = BlackRook;
7198     } else if (board[fromY][fromX] == BlackPawn
7199                && toY == 0
7200                && gameInfo.variant != VariantXiangqi
7201                ) {
7202         /* black pawn promotion */
7203         board[0][toX] = CharToPiece(ToLower(promoChar));
7204         if (board[0][toX] == EmptySquare) {
7205             board[0][toX] = BlackQueen;
7206         }
7207         if(gameInfo.variant==VariantBughouse ||
7208            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7209             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7210         board[fromY][fromX] = EmptySquare;
7211     } else if ((fromY == 3)
7212                && (toX != fromX)
7213                && gameInfo.variant != VariantXiangqi
7214                && gameInfo.variant != VariantBerolina
7215                && (board[fromY][fromX] == BlackPawn)
7216                && (board[toY][toX] == EmptySquare)) {
7217         board[fromY][fromX] = EmptySquare;
7218         board[toY][toX] = BlackPawn;
7219         captured = board[toY + 1][toX];
7220         board[toY + 1][toX] = EmptySquare;
7221     } else if ((fromY == 3)
7222                && (toX == fromX)
7223                && gameInfo.variant == VariantBerolina
7224                && (board[fromY][fromX] == BlackPawn)
7225                && (board[toY][toX] == EmptySquare)) {
7226         board[fromY][fromX] = EmptySquare;
7227         board[toY][toX] = BlackPawn;
7228         if(oldEP & EP_BEROLIN_A) {
7229                 captured = board[fromY][fromX-1];
7230                 board[fromY][fromX-1] = EmptySquare;
7231         }else{  captured = board[fromY][fromX+1];
7232                 board[fromY][fromX+1] = EmptySquare;
7233         }
7234     } else {
7235         board[toY][toX] = board[fromY][fromX];
7236         board[fromY][fromX] = EmptySquare;
7237     }
7238
7239     /* [HGM] now we promote for Shogi, if needed */
7240     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7241         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7242   }
7243
7244     if (gameInfo.holdingsWidth != 0) {
7245
7246       /* !!A lot more code needs to be written to support holdings  */
7247       /* [HGM] OK, so I have written it. Holdings are stored in the */
7248       /* penultimate board files, so they are automaticlly stored   */
7249       /* in the game history.                                       */
7250       if (fromY == DROP_RANK) {
7251         /* Delete from holdings, by decreasing count */
7252         /* and erasing image if necessary            */
7253         p = (int) fromX;
7254         if(p < (int) BlackPawn) { /* white drop */
7255              p -= (int)WhitePawn;
7256              if(p >= gameInfo.holdingsSize) p = 0;
7257              if(--board[p][BOARD_WIDTH-2] == 0)
7258                   board[p][BOARD_WIDTH-1] = EmptySquare;
7259         } else {                  /* black drop */
7260              p -= (int)BlackPawn;
7261              if(p >= gameInfo.holdingsSize) p = 0;
7262              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7263                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7264         }
7265       }
7266       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7267           && gameInfo.variant != VariantBughouse        ) {
7268         /* [HGM] holdings: Add to holdings, if holdings exist */
7269         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7270                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7271                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7272         }
7273         p = (int) captured;
7274         if (p >= (int) BlackPawn) {
7275           p -= (int)BlackPawn;
7276           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7277                   /* in Shogi restore piece to its original  first */
7278                   captured = (ChessSquare) (DEMOTED captured);
7279                   p = DEMOTED p;
7280           }
7281           p = PieceToNumber((ChessSquare)p);
7282           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7283           board[p][BOARD_WIDTH-2]++;
7284           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7285         } else {
7286           p -= (int)WhitePawn;
7287           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7288                   captured = (ChessSquare) (DEMOTED captured);
7289                   p = DEMOTED p;
7290           }
7291           p = PieceToNumber((ChessSquare)p);
7292           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7293           board[BOARD_HEIGHT-1-p][1]++;
7294           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7295         }
7296       }
7297
7298     } else if (gameInfo.variant == VariantAtomic) {
7299       if (captured != EmptySquare) {
7300         int y, x;
7301         for (y = toY-1; y <= toY+1; y++) {
7302           for (x = toX-1; x <= toX+1; x++) {
7303             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7304                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7305               board[y][x] = EmptySquare;
7306             }
7307           }
7308         }
7309         board[toY][toX] = EmptySquare;
7310       }
7311     }
7312     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7313         /* [HGM] Shogi promotions */
7314         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7315     }
7316
7317     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7318                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7319         // [HGM] superchess: take promotion piece out of holdings
7320         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7321         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7322             if(!--board[k][BOARD_WIDTH-2])
7323                 board[k][BOARD_WIDTH-1] = EmptySquare;
7324         } else {
7325             if(!--board[BOARD_HEIGHT-1-k][1])
7326                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7327         }
7328     }
7329
7330 }
7331
7332 /* Updates forwardMostMove */
7333 void
7334 MakeMove(fromX, fromY, toX, toY, promoChar)
7335      int fromX, fromY, toX, toY;
7336      int promoChar;
7337 {
7338 //    forwardMostMove++; // [HGM] bare: moved downstream
7339
7340     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7341         int timeLeft; static int lastLoadFlag=0; int king, piece;
7342         piece = boards[forwardMostMove][fromY][fromX];
7343         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7344         if(gameInfo.variant == VariantKnightmate)
7345             king += (int) WhiteUnicorn - (int) WhiteKing;
7346         if(forwardMostMove == 0) {
7347             if(blackPlaysFirst) 
7348                 fprintf(serverMoves, "%s;", second.tidy);
7349             fprintf(serverMoves, "%s;", first.tidy);
7350             if(!blackPlaysFirst) 
7351                 fprintf(serverMoves, "%s;", second.tidy);
7352         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7353         lastLoadFlag = loadFlag;
7354         // print base move
7355         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7356         // print castling suffix
7357         if( toY == fromY && piece == king ) {
7358             if(toX-fromX > 1)
7359                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7360             if(fromX-toX >1)
7361                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7362         }
7363         // e.p. suffix
7364         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7365              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7366              boards[forwardMostMove][toY][toX] == EmptySquare
7367              && fromX != toX )
7368                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7369         // promotion suffix
7370         if(promoChar != NULLCHAR)
7371                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7372         if(!loadFlag) {
7373             fprintf(serverMoves, "/%d/%d",
7374                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7375             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7376             else                      timeLeft = blackTimeRemaining/1000;
7377             fprintf(serverMoves, "/%d", timeLeft);
7378         }
7379         fflush(serverMoves);
7380     }
7381
7382     if (forwardMostMove+1 >= MAX_MOVES) {
7383       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7384                         0, 1);
7385       return;
7386     }
7387     if (commentList[forwardMostMove+1] != NULL) {
7388         free(commentList[forwardMostMove+1]);
7389         commentList[forwardMostMove+1] = NULL;
7390     }
7391     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7392     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7393     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7394                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7395     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7396     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7397     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7398     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7399     gameInfo.result = GameUnfinished;
7400     if (gameInfo.resultDetails != NULL) {
7401         free(gameInfo.resultDetails);
7402         gameInfo.resultDetails = NULL;
7403     }
7404     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7405                               moveList[forwardMostMove - 1]);
7406     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7407                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7408                              fromY, fromX, toY, toX, promoChar,
7409                              parseList[forwardMostMove - 1]);
7410     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7411                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7412                             castlingRights[forwardMostMove]) ) {
7413       case MT_NONE:
7414       case MT_STALEMATE:
7415       default:
7416         break;
7417       case MT_CHECK:
7418         if(gameInfo.variant != VariantShogi)
7419             strcat(parseList[forwardMostMove - 1], "+");
7420         break;
7421       case MT_CHECKMATE:
7422       case MT_STAINMATE:
7423         strcat(parseList[forwardMostMove - 1], "#");
7424         break;
7425     }
7426     if (appData.debugMode) {
7427         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7428     }
7429
7430 }
7431
7432 /* Updates currentMove if not pausing */
7433 void
7434 ShowMove(fromX, fromY, toX, toY)
7435 {
7436     int instant = (gameMode == PlayFromGameFile) ?
7437         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7438     if(appData.noGUI) return;
7439     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7440         if (!instant) {
7441             if (forwardMostMove == currentMove + 1) {
7442                 AnimateMove(boards[forwardMostMove - 1],
7443                             fromX, fromY, toX, toY);
7444             }
7445             if (appData.highlightLastMove) {
7446                 SetHighlights(fromX, fromY, toX, toY);
7447             }
7448         }
7449         currentMove = forwardMostMove;
7450     }
7451
7452     if (instant) return;
7453
7454     DisplayMove(currentMove - 1);
7455     DrawPosition(FALSE, boards[currentMove]);
7456     DisplayBothClocks();
7457     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7458 }
7459
7460 void SendEgtPath(ChessProgramState *cps)
7461 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7462         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7463
7464         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7465
7466         while(*p) {
7467             char c, *q = name+1, *r, *s;
7468
7469             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7470             while(*p && *p != ',') *q++ = *p++;
7471             *q++ = ':'; *q = 0;
7472             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7473                 strcmp(name, ",nalimov:") == 0 ) {
7474                 // take nalimov path from the menu-changeable option first, if it is defined
7475                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7476                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7477             } else
7478             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7479                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7480                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7481                 s = r = StrStr(s, ":") + 1; // beginning of path info
7482                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7483                 c = *r; *r = 0;             // temporarily null-terminate path info
7484                     *--q = 0;               // strip of trailig ':' from name
7485                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7486                 *r = c;
7487                 SendToProgram(buf,cps);     // send egtbpath command for this format
7488             }
7489             if(*p == ',') p++; // read away comma to position for next format name
7490         }
7491 }
7492
7493 void
7494 InitChessProgram(cps, setup)
7495      ChessProgramState *cps;
7496      int setup; /* [HGM] needed to setup FRC opening position */
7497 {
7498     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7499     if (appData.noChessProgram) return;
7500     hintRequested = FALSE;
7501     bookRequested = FALSE;
7502
7503     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7504     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7505     if(cps->memSize) { /* [HGM] memory */
7506         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7507         SendToProgram(buf, cps);
7508     }
7509     SendEgtPath(cps); /* [HGM] EGT */
7510     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7511         sprintf(buf, "cores %d\n", appData.smpCores);
7512         SendToProgram(buf, cps);
7513     }
7514
7515     SendToProgram(cps->initString, cps);
7516     if (gameInfo.variant != VariantNormal &&
7517         gameInfo.variant != VariantLoadable
7518         /* [HGM] also send variant if board size non-standard */
7519         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7520                                             ) {
7521       char *v = VariantName(gameInfo.variant);
7522       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7523         /* [HGM] in protocol 1 we have to assume all variants valid */
7524         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7525         DisplayFatalError(buf, 0, 1);
7526         return;
7527       }
7528
7529       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7530       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7531       if( gameInfo.variant == VariantXiangqi )
7532            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7533       if( gameInfo.variant == VariantShogi )
7534            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7535       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7536            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7537       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7538                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7539            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7540       if( gameInfo.variant == VariantCourier )
7541            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7542       if( gameInfo.variant == VariantSuper )
7543            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7544       if( gameInfo.variant == VariantGreat )
7545            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7546
7547       if(overruled) {
7548            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7549                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7550            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7551            if(StrStr(cps->variants, b) == NULL) { 
7552                // specific sized variant not known, check if general sizing allowed
7553                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7554                    if(StrStr(cps->variants, "boardsize") == NULL) {
7555                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7556                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7557                        DisplayFatalError(buf, 0, 1);
7558                        return;
7559                    }
7560                    /* [HGM] here we really should compare with the maximum supported board size */
7561                }
7562            }
7563       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7564       sprintf(buf, "variant %s\n", b);
7565       SendToProgram(buf, cps);
7566     }
7567     currentlyInitializedVariant = gameInfo.variant;
7568
7569     /* [HGM] send opening position in FRC to first engine */
7570     if(setup) {
7571           SendToProgram("force\n", cps);
7572           SendBoard(cps, 0);
7573           /* engine is now in force mode! Set flag to wake it up after first move. */
7574           setboardSpoiledMachineBlack = 1;
7575     }
7576
7577     if (cps->sendICS) {
7578       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7579       SendToProgram(buf, cps);
7580     }
7581     cps->maybeThinking = FALSE;
7582     cps->offeredDraw = 0;
7583     if (!appData.icsActive) {
7584         SendTimeControl(cps, movesPerSession, timeControl,
7585                         timeIncrement, appData.searchDepth,
7586                         searchTime);
7587     }
7588     if (appData.showThinking 
7589         // [HGM] thinking: four options require thinking output to be sent
7590         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7591                                 ) {
7592         SendToProgram("post\n", cps);
7593     }
7594     SendToProgram("hard\n", cps);
7595     if (!appData.ponderNextMove) {
7596         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7597            it without being sure what state we are in first.  "hard"
7598            is not a toggle, so that one is OK.
7599          */
7600         SendToProgram("easy\n", cps);
7601     }
7602     if (cps->usePing) {
7603       sprintf(buf, "ping %d\n", ++cps->lastPing);
7604       SendToProgram(buf, cps);
7605     }
7606     cps->initDone = TRUE;
7607 }   
7608
7609
7610 void
7611 StartChessProgram(cps)
7612      ChessProgramState *cps;
7613 {
7614     char buf[MSG_SIZ];
7615     int err;
7616
7617     if (appData.noChessProgram) return;
7618     cps->initDone = FALSE;
7619
7620     if (strcmp(cps->host, "localhost") == 0) {
7621         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7622     } else if (*appData.remoteShell == NULLCHAR) {
7623         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7624     } else {
7625         if (*appData.remoteUser == NULLCHAR) {
7626           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7627                     cps->program);
7628         } else {
7629           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7630                     cps->host, appData.remoteUser, cps->program);
7631         }
7632         err = StartChildProcess(buf, "", &cps->pr);
7633     }
7634     
7635     if (err != 0) {
7636         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7637         DisplayFatalError(buf, err, 1);
7638         cps->pr = NoProc;
7639         cps->isr = NULL;
7640         return;
7641     }
7642     
7643     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7644     if (cps->protocolVersion > 1) {
7645       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7646       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7647       cps->comboCnt = 0;  //                and values of combo boxes
7648       SendToProgram(buf, cps);
7649     } else {
7650       SendToProgram("xboard\n", cps);
7651     }
7652 }
7653
7654
7655 void
7656 TwoMachinesEventIfReady P((void))
7657 {
7658   if (first.lastPing != first.lastPong) {
7659     DisplayMessage("", _("Waiting for first chess program"));
7660     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7661     return;
7662   }
7663   if (second.lastPing != second.lastPong) {
7664     DisplayMessage("", _("Waiting for second chess program"));
7665     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7666     return;
7667   }
7668   ThawUI();
7669   TwoMachinesEvent();
7670 }
7671
7672 void
7673 NextMatchGame P((void))
7674 {
7675     int index; /* [HGM] autoinc: step lod index during match */
7676     Reset(FALSE, TRUE);
7677     if (*appData.loadGameFile != NULLCHAR) {
7678         index = appData.loadGameIndex;
7679         if(index < 0) { // [HGM] autoinc
7680             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7681             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7682         } 
7683         LoadGameFromFile(appData.loadGameFile,
7684                          index,
7685                          appData.loadGameFile, FALSE);
7686     } else if (*appData.loadPositionFile != NULLCHAR) {
7687         index = appData.loadPositionIndex;
7688         if(index < 0) { // [HGM] autoinc
7689             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7690             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7691         } 
7692         LoadPositionFromFile(appData.loadPositionFile,
7693                              index,
7694                              appData.loadPositionFile);
7695     }
7696     TwoMachinesEventIfReady();
7697 }
7698
7699 void UserAdjudicationEvent( int result )
7700 {
7701     ChessMove gameResult = GameIsDrawn;
7702
7703     if( result > 0 ) {
7704         gameResult = WhiteWins;
7705     }
7706     else if( result < 0 ) {
7707         gameResult = BlackWins;
7708     }
7709
7710     if( gameMode == TwoMachinesPlay ) {
7711         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7712     }
7713 }
7714
7715
7716 // [HGM] save: calculate checksum of game to make games easily identifiable
7717 int StringCheckSum(char *s)
7718 {
7719         int i = 0;
7720         if(s==NULL) return 0;
7721         while(*s) i = i*259 + *s++;
7722         return i;
7723 }
7724
7725 int GameCheckSum()
7726 {
7727         int i, sum=0;
7728         for(i=backwardMostMove; i<forwardMostMove; i++) {
7729                 sum += pvInfoList[i].depth;
7730                 sum += StringCheckSum(parseList[i]);
7731                 sum += StringCheckSum(commentList[i]);
7732                 sum *= 261;
7733         }
7734         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7735         return sum + StringCheckSum(commentList[i]);
7736 } // end of save patch
7737
7738 void
7739 GameEnds(result, resultDetails, whosays)
7740      ChessMove result;
7741      char *resultDetails;
7742      int whosays;
7743 {
7744     GameMode nextGameMode;
7745     int isIcsGame;
7746     char buf[MSG_SIZ];
7747
7748     if(endingGame) return; /* [HGM] crash: forbid recursion */
7749     endingGame = 1;
7750
7751     if (appData.debugMode) {
7752       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7753               result, resultDetails ? resultDetails : "(null)", whosays);
7754     }
7755
7756     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7757         /* If we are playing on ICS, the server decides when the
7758            game is over, but the engine can offer to draw, claim 
7759            a draw, or resign. 
7760          */
7761 #if ZIPPY
7762         if (appData.zippyPlay && first.initDone) {
7763             if (result == GameIsDrawn) {
7764                 /* In case draw still needs to be claimed */
7765                 SendToICS(ics_prefix);
7766                 SendToICS("draw\n");
7767             } else if (StrCaseStr(resultDetails, "resign")) {
7768                 SendToICS(ics_prefix);
7769                 SendToICS("resign\n");
7770             }
7771         }
7772 #endif
7773         endingGame = 0; /* [HGM] crash */
7774         return;
7775     }
7776
7777     /* If we're loading the game from a file, stop */
7778     if (whosays == GE_FILE) {
7779       (void) StopLoadGameTimer();
7780       gameFileFP = NULL;
7781     }
7782
7783     /* Cancel draw offers */
7784     first.offeredDraw = second.offeredDraw = 0;
7785
7786     /* If this is an ICS game, only ICS can really say it's done;
7787        if not, anyone can. */
7788     isIcsGame = (gameMode == IcsPlayingWhite || 
7789                  gameMode == IcsPlayingBlack || 
7790                  gameMode == IcsObserving    || 
7791                  gameMode == IcsExamining);
7792
7793     if (!isIcsGame || whosays == GE_ICS) {
7794         /* OK -- not an ICS game, or ICS said it was done */
7795         StopClocks();
7796         if (!isIcsGame && !appData.noChessProgram) 
7797           SetUserThinkingEnables();
7798     
7799         /* [HGM] if a machine claims the game end we verify this claim */
7800         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7801             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7802                 char claimer;
7803                 ChessMove trueResult = (ChessMove) -1;
7804
7805                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7806                                             first.twoMachinesColor[0] :
7807                                             second.twoMachinesColor[0] ;
7808
7809                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7810                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7811                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7812                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7813                 } else
7814                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7815                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7816                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7817                 } else
7818                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7819                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7820                 }
7821
7822                 // now verify win claims, but not in drop games, as we don't understand those yet
7823                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7824                                                  || gameInfo.variant == VariantGreat) &&
7825                     (result == WhiteWins && claimer == 'w' ||
7826                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7827                       if (appData.debugMode) {
7828                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7829                                 result, epStatus[forwardMostMove], forwardMostMove);
7830                       }
7831                       if(result != trueResult) {
7832                               sprintf(buf, "False win claim: '%s'", resultDetails);
7833                               result = claimer == 'w' ? BlackWins : WhiteWins;
7834                               resultDetails = buf;
7835                       }
7836                 } else
7837                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7838                     && (forwardMostMove <= backwardMostMove ||
7839                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7840                         (claimer=='b')==(forwardMostMove&1))
7841                                                                                   ) {
7842                       /* [HGM] verify: draws that were not flagged are false claims */
7843                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7844                       result = claimer == 'w' ? BlackWins : WhiteWins;
7845                       resultDetails = buf;
7846                 }
7847                 /* (Claiming a loss is accepted no questions asked!) */
7848             }
7849             /* [HGM] bare: don't allow bare King to win */
7850             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7851                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7852                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7853                && result != GameIsDrawn)
7854             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7855                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7856                         int p = (int)boards[forwardMostMove][i][j] - color;
7857                         if(p >= 0 && p <= (int)WhiteKing) k++;
7858                 }
7859                 if (appData.debugMode) {
7860                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7861                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7862                 }
7863                 if(k <= 1) {
7864                         result = GameIsDrawn;
7865                         sprintf(buf, "%s but bare king", resultDetails);
7866                         resultDetails = buf;
7867                 }
7868             }
7869         }
7870
7871
7872         if(serverMoves != NULL && !loadFlag) { char c = '=';
7873             if(result==WhiteWins) c = '+';
7874             if(result==BlackWins) c = '-';
7875             if(resultDetails != NULL)
7876                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7877         }
7878         if (resultDetails != NULL) {
7879             gameInfo.result = result;
7880             gameInfo.resultDetails = StrSave(resultDetails);
7881
7882             /* display last move only if game was not loaded from file */
7883             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7884                 DisplayMove(currentMove - 1);
7885     
7886             if (forwardMostMove != 0) {
7887                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7888                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7889                                                                 ) {
7890                     if (*appData.saveGameFile != NULLCHAR) {
7891                         SaveGameToFile(appData.saveGameFile, TRUE);
7892                     } else if (appData.autoSaveGames) {
7893                         AutoSaveGame();
7894                     }
7895                     if (*appData.savePositionFile != NULLCHAR) {
7896                         SavePositionToFile(appData.savePositionFile);
7897                     }
7898                 }
7899             }
7900
7901             /* Tell program how game ended in case it is learning */
7902             /* [HGM] Moved this to after saving the PGN, just in case */
7903             /* engine died and we got here through time loss. In that */
7904             /* case we will get a fatal error writing the pipe, which */
7905             /* would otherwise lose us the PGN.                       */
7906             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7907             /* output during GameEnds should never be fatal anymore   */
7908             if (gameMode == MachinePlaysWhite ||
7909                 gameMode == MachinePlaysBlack ||
7910                 gameMode == TwoMachinesPlay ||
7911                 gameMode == IcsPlayingWhite ||
7912                 gameMode == IcsPlayingBlack ||
7913                 gameMode == BeginningOfGame) {
7914                 char buf[MSG_SIZ];
7915                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7916                         resultDetails);
7917                 if (first.pr != NoProc) {
7918                     SendToProgram(buf, &first);
7919                 }
7920                 if (second.pr != NoProc &&
7921                     gameMode == TwoMachinesPlay) {
7922                     SendToProgram(buf, &second);
7923                 }
7924             }
7925         }
7926
7927         if (appData.icsActive) {
7928             if (appData.quietPlay &&
7929                 (gameMode == IcsPlayingWhite ||
7930                  gameMode == IcsPlayingBlack)) {
7931                 SendToICS(ics_prefix);
7932                 SendToICS("set shout 1\n");
7933             }
7934             nextGameMode = IcsIdle;
7935             ics_user_moved = FALSE;
7936             /* clean up premove.  It's ugly when the game has ended and the
7937              * premove highlights are still on the board.
7938              */
7939             if (gotPremove) {
7940               gotPremove = FALSE;
7941               ClearPremoveHighlights();
7942               DrawPosition(FALSE, boards[currentMove]);
7943             }
7944             if (whosays == GE_ICS) {
7945                 switch (result) {
7946                 case WhiteWins:
7947                     if (gameMode == IcsPlayingWhite)
7948                         PlayIcsWinSound();
7949                     else if(gameMode == IcsPlayingBlack)
7950                         PlayIcsLossSound();
7951                     break;
7952                 case BlackWins:
7953                     if (gameMode == IcsPlayingBlack)
7954                         PlayIcsWinSound();
7955                     else if(gameMode == IcsPlayingWhite)
7956                         PlayIcsLossSound();
7957                     break;
7958                 case GameIsDrawn:
7959                     PlayIcsDrawSound();
7960                     break;
7961                 default:
7962                     PlayIcsUnfinishedSound();
7963                 }
7964             }
7965         } else if (gameMode == EditGame ||
7966                    gameMode == PlayFromGameFile || 
7967                    gameMode == AnalyzeMode || 
7968                    gameMode == AnalyzeFile) {
7969             nextGameMode = gameMode;
7970         } else {
7971             nextGameMode = EndOfGame;
7972         }
7973         pausing = FALSE;
7974         ModeHighlight();
7975     } else {
7976         nextGameMode = gameMode;
7977     }
7978
7979     if (appData.noChessProgram) {
7980         gameMode = nextGameMode;
7981         ModeHighlight();
7982         endingGame = 0; /* [HGM] crash */
7983         return;
7984     }
7985
7986     if (first.reuse) {
7987         /* Put first chess program into idle state */
7988         if (first.pr != NoProc &&
7989             (gameMode == MachinePlaysWhite ||
7990              gameMode == MachinePlaysBlack ||
7991              gameMode == TwoMachinesPlay ||
7992              gameMode == IcsPlayingWhite ||
7993              gameMode == IcsPlayingBlack ||
7994              gameMode == BeginningOfGame)) {
7995             SendToProgram("force\n", &first);
7996             if (first.usePing) {
7997               char buf[MSG_SIZ];
7998               sprintf(buf, "ping %d\n", ++first.lastPing);
7999               SendToProgram(buf, &first);
8000             }
8001         }
8002     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8003         /* Kill off first chess program */
8004         if (first.isr != NULL)
8005           RemoveInputSource(first.isr);
8006         first.isr = NULL;
8007     
8008         if (first.pr != NoProc) {
8009             ExitAnalyzeMode();
8010             DoSleep( appData.delayBeforeQuit );
8011             SendToProgram("quit\n", &first);
8012             DoSleep( appData.delayAfterQuit );
8013             DestroyChildProcess(first.pr, first.useSigterm);
8014         }
8015         first.pr = NoProc;
8016     }
8017     if (second.reuse) {
8018         /* Put second chess program into idle state */
8019         if (second.pr != NoProc &&
8020             gameMode == TwoMachinesPlay) {
8021             SendToProgram("force\n", &second);
8022             if (second.usePing) {
8023               char buf[MSG_SIZ];
8024               sprintf(buf, "ping %d\n", ++second.lastPing);
8025               SendToProgram(buf, &second);
8026             }
8027         }
8028     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8029         /* Kill off second chess program */
8030         if (second.isr != NULL)
8031           RemoveInputSource(second.isr);
8032         second.isr = NULL;
8033     
8034         if (second.pr != NoProc) {
8035             DoSleep( appData.delayBeforeQuit );
8036             SendToProgram("quit\n", &second);
8037             DoSleep( appData.delayAfterQuit );
8038             DestroyChildProcess(second.pr, second.useSigterm);
8039         }
8040         second.pr = NoProc;
8041     }
8042
8043     if (matchMode && gameMode == TwoMachinesPlay) {
8044         switch (result) {
8045         case WhiteWins:
8046           if (first.twoMachinesColor[0] == 'w') {
8047             first.matchWins++;
8048           } else {
8049             second.matchWins++;
8050           }
8051           break;
8052         case BlackWins:
8053           if (first.twoMachinesColor[0] == 'b') {
8054             first.matchWins++;
8055           } else {
8056             second.matchWins++;
8057           }
8058           break;
8059         default:
8060           break;
8061         }
8062         if (matchGame < appData.matchGames) {
8063             char *tmp;
8064             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8065                 tmp = first.twoMachinesColor;
8066                 first.twoMachinesColor = second.twoMachinesColor;
8067                 second.twoMachinesColor = tmp;
8068             }
8069             gameMode = nextGameMode;
8070             matchGame++;
8071             if(appData.matchPause>10000 || appData.matchPause<10)
8072                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8073             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8074             endingGame = 0; /* [HGM] crash */
8075             return;
8076         } else {
8077             char buf[MSG_SIZ];
8078             gameMode = nextGameMode;
8079             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8080                     first.tidy, second.tidy,
8081                     first.matchWins, second.matchWins,
8082                     appData.matchGames - (first.matchWins + second.matchWins));
8083             DisplayFatalError(buf, 0, 0);
8084         }
8085     }
8086     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8087         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8088       ExitAnalyzeMode();
8089     gameMode = nextGameMode;
8090     ModeHighlight();
8091     endingGame = 0;  /* [HGM] crash */
8092 }
8093
8094 /* Assumes program was just initialized (initString sent).
8095    Leaves program in force mode. */
8096 void
8097 FeedMovesToProgram(cps, upto) 
8098      ChessProgramState *cps;
8099      int upto;
8100 {
8101     int i;
8102     
8103     if (appData.debugMode)
8104       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8105               startedFromSetupPosition ? "position and " : "",
8106               backwardMostMove, upto, cps->which);
8107     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8108         // [HGM] variantswitch: make engine aware of new variant
8109         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8110                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8111         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8112         SendToProgram(buf, cps);
8113         currentlyInitializedVariant = gameInfo.variant;
8114     }
8115     SendToProgram("force\n", cps);
8116     if (startedFromSetupPosition) {
8117         SendBoard(cps, backwardMostMove);
8118     if (appData.debugMode) {
8119         fprintf(debugFP, "feedMoves\n");
8120     }
8121     }
8122     for (i = backwardMostMove; i < upto; i++) {
8123         SendMoveToProgram(i, cps);
8124     }
8125 }
8126
8127
8128 void
8129 ResurrectChessProgram()
8130 {
8131      /* The chess program may have exited.
8132         If so, restart it and feed it all the moves made so far. */
8133
8134     if (appData.noChessProgram || first.pr != NoProc) return;
8135     
8136     StartChessProgram(&first);
8137     InitChessProgram(&first, FALSE);
8138     FeedMovesToProgram(&first, currentMove);
8139
8140     if (!first.sendTime) {
8141         /* can't tell gnuchess what its clock should read,
8142            so we bow to its notion. */
8143         ResetClocks();
8144         timeRemaining[0][currentMove] = whiteTimeRemaining;
8145         timeRemaining[1][currentMove] = blackTimeRemaining;
8146     }
8147
8148     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8149                 appData.icsEngineAnalyze) && first.analysisSupport) {
8150       SendToProgram("analyze\n", &first);
8151       first.analyzing = TRUE;
8152     }
8153 }
8154
8155 /*
8156  * Button procedures
8157  */
8158 void
8159 Reset(redraw, init)
8160      int redraw, init;
8161 {
8162     int i;
8163
8164     if (appData.debugMode) {
8165         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8166                 redraw, init, gameMode);
8167     }
8168     pausing = pauseExamInvalid = FALSE;
8169     startedFromSetupPosition = blackPlaysFirst = FALSE;
8170     firstMove = TRUE;
8171     whiteFlag = blackFlag = FALSE;
8172     userOfferedDraw = FALSE;
8173     hintRequested = bookRequested = FALSE;
8174     first.maybeThinking = FALSE;
8175     second.maybeThinking = FALSE;
8176     first.bookSuspend = FALSE; // [HGM] book
8177     second.bookSuspend = FALSE;
8178     thinkOutput[0] = NULLCHAR;
8179     lastHint[0] = NULLCHAR;
8180     ClearGameInfo(&gameInfo);
8181     gameInfo.variant = StringToVariant(appData.variant);
8182     ics_user_moved = ics_clock_paused = FALSE;
8183     ics_getting_history = H_FALSE;
8184     ics_gamenum = -1;
8185     white_holding[0] = black_holding[0] = NULLCHAR;
8186     ClearProgramStats();
8187     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8188     
8189     ResetFrontEnd();
8190     ClearHighlights();
8191     flipView = appData.flipView;
8192     ClearPremoveHighlights();
8193     gotPremove = FALSE;
8194     alarmSounded = FALSE;
8195
8196     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8197     if(appData.serverMovesName != NULL) {
8198         /* [HGM] prepare to make moves file for broadcasting */
8199         clock_t t = clock();
8200         if(serverMoves != NULL) fclose(serverMoves);
8201         serverMoves = fopen(appData.serverMovesName, "r");
8202         if(serverMoves != NULL) {
8203             fclose(serverMoves);
8204             /* delay 15 sec before overwriting, so all clients can see end */
8205             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8206         }
8207         serverMoves = fopen(appData.serverMovesName, "w");
8208     }
8209
8210     ExitAnalyzeMode();
8211     gameMode = BeginningOfGame;
8212     ModeHighlight();
8213     if(appData.icsActive) gameInfo.variant = VariantNormal;
8214     currentMove = forwardMostMove = backwardMostMove = 0;
8215     InitPosition(redraw);
8216     for (i = 0; i < MAX_MOVES; i++) {
8217         if (commentList[i] != NULL) {
8218             free(commentList[i]);
8219             commentList[i] = NULL;
8220         }
8221     }
8222     ResetClocks();
8223     timeRemaining[0][0] = whiteTimeRemaining;
8224     timeRemaining[1][0] = blackTimeRemaining;
8225     if (first.pr == NULL) {
8226         StartChessProgram(&first);
8227     }
8228     if (init) {
8229             InitChessProgram(&first, startedFromSetupPosition);
8230     }
8231     DisplayTitle("");
8232     DisplayMessage("", "");
8233     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8234     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8235 }
8236
8237 void
8238 AutoPlayGameLoop()
8239 {
8240     for (;;) {
8241         if (!AutoPlayOneMove())
8242           return;
8243         if (matchMode || appData.timeDelay == 0)
8244           continue;
8245         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8246           return;
8247         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8248         break;
8249     }
8250 }
8251
8252
8253 int
8254 AutoPlayOneMove()
8255 {
8256     int fromX, fromY, toX, toY;
8257
8258     if (appData.debugMode) {
8259       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8260     }
8261
8262     if (gameMode != PlayFromGameFile)
8263       return FALSE;
8264
8265     if (currentMove >= forwardMostMove) {
8266       gameMode = EditGame;
8267       ModeHighlight();
8268
8269       /* [AS] Clear current move marker at the end of a game */
8270       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8271
8272       return FALSE;
8273     }
8274     
8275     toX = moveList[currentMove][2] - AAA;
8276     toY = moveList[currentMove][3] - ONE;
8277
8278     if (moveList[currentMove][1] == '@') {
8279         if (appData.highlightLastMove) {
8280             SetHighlights(-1, -1, toX, toY);
8281         }
8282     } else {
8283         fromX = moveList[currentMove][0] - AAA;
8284         fromY = moveList[currentMove][1] - ONE;
8285
8286         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8287
8288         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8289
8290         if (appData.highlightLastMove) {
8291             SetHighlights(fromX, fromY, toX, toY);
8292         }
8293     }
8294     DisplayMove(currentMove);
8295     SendMoveToProgram(currentMove++, &first);
8296     DisplayBothClocks();
8297     DrawPosition(FALSE, boards[currentMove]);
8298     // [HGM] PV info: always display, routine tests if empty
8299     DisplayComment(currentMove - 1, commentList[currentMove]);
8300     return TRUE;
8301 }
8302
8303
8304 int
8305 LoadGameOneMove(readAhead)
8306      ChessMove readAhead;
8307 {
8308     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8309     char promoChar = NULLCHAR;
8310     ChessMove moveType;
8311     char move[MSG_SIZ];
8312     char *p, *q;
8313     
8314     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8315         gameMode != AnalyzeMode && gameMode != Training) {
8316         gameFileFP = NULL;
8317         return FALSE;
8318     }
8319     
8320     yyboardindex = forwardMostMove;
8321     if (readAhead != (ChessMove)0) {
8322       moveType = readAhead;
8323     } else {
8324       if (gameFileFP == NULL)
8325           return FALSE;
8326       moveType = (ChessMove) yylex();
8327     }
8328     
8329     done = FALSE;
8330     switch (moveType) {
8331       case Comment:
8332         if (appData.debugMode) 
8333           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8334         p = yy_text;
8335         if (*p == '{' || *p == '[' || *p == '(') {
8336             p[strlen(p) - 1] = NULLCHAR;
8337             p++;
8338         }
8339
8340         /* append the comment but don't display it */
8341         while (*p == '\n') p++;
8342         AppendComment(currentMove, p);
8343         return TRUE;
8344
8345       case WhiteCapturesEnPassant:
8346       case BlackCapturesEnPassant:
8347       case WhitePromotionChancellor:
8348       case BlackPromotionChancellor:
8349       case WhitePromotionArchbishop:
8350       case BlackPromotionArchbishop:
8351       case WhitePromotionCentaur:
8352       case BlackPromotionCentaur:
8353       case WhitePromotionQueen:
8354       case BlackPromotionQueen:
8355       case WhitePromotionRook:
8356       case BlackPromotionRook:
8357       case WhitePromotionBishop:
8358       case BlackPromotionBishop:
8359       case WhitePromotionKnight:
8360       case BlackPromotionKnight:
8361       case WhitePromotionKing:
8362       case BlackPromotionKing:
8363       case NormalMove:
8364       case WhiteKingSideCastle:
8365       case WhiteQueenSideCastle:
8366       case BlackKingSideCastle:
8367       case BlackQueenSideCastle:
8368       case WhiteKingSideCastleWild:
8369       case WhiteQueenSideCastleWild:
8370       case BlackKingSideCastleWild:
8371       case BlackQueenSideCastleWild:
8372       /* PUSH Fabien */
8373       case WhiteHSideCastleFR:
8374       case WhiteASideCastleFR:
8375       case BlackHSideCastleFR:
8376       case BlackASideCastleFR:
8377       /* POP Fabien */
8378         if (appData.debugMode)
8379           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8380         fromX = currentMoveString[0] - AAA;
8381         fromY = currentMoveString[1] - ONE;
8382         toX = currentMoveString[2] - AAA;
8383         toY = currentMoveString[3] - ONE;
8384         promoChar = currentMoveString[4];
8385         break;
8386
8387       case WhiteDrop:
8388       case BlackDrop:
8389         if (appData.debugMode)
8390           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8391         fromX = moveType == WhiteDrop ?
8392           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8393         (int) CharToPiece(ToLower(currentMoveString[0]));
8394         fromY = DROP_RANK;
8395         toX = currentMoveString[2] - AAA;
8396         toY = currentMoveString[3] - ONE;
8397         break;
8398
8399       case WhiteWins:
8400       case BlackWins:
8401       case GameIsDrawn:
8402       case GameUnfinished:
8403         if (appData.debugMode)
8404           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8405         p = strchr(yy_text, '{');
8406         if (p == NULL) p = strchr(yy_text, '(');
8407         if (p == NULL) {
8408             p = yy_text;
8409             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8410         } else {
8411             q = strchr(p, *p == '{' ? '}' : ')');
8412             if (q != NULL) *q = NULLCHAR;
8413             p++;
8414         }
8415         GameEnds(moveType, p, GE_FILE);
8416         done = TRUE;
8417         if (cmailMsgLoaded) {
8418             ClearHighlights();
8419             flipView = WhiteOnMove(currentMove);
8420             if (moveType == GameUnfinished) flipView = !flipView;
8421             if (appData.debugMode)
8422               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8423         }
8424         break;
8425
8426       case (ChessMove) 0:       /* end of file */
8427         if (appData.debugMode)
8428           fprintf(debugFP, "Parser hit end of file\n");
8429         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8430                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8431           case MT_NONE:
8432           case MT_CHECK:
8433             break;
8434           case MT_CHECKMATE:
8435           case MT_STAINMATE:
8436             if (WhiteOnMove(currentMove)) {
8437                 GameEnds(BlackWins, "Black mates", GE_FILE);
8438             } else {
8439                 GameEnds(WhiteWins, "White mates", GE_FILE);
8440             }
8441             break;
8442           case MT_STALEMATE:
8443             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8444             break;
8445         }
8446         done = TRUE;
8447         break;
8448
8449       case MoveNumberOne:
8450         if (lastLoadGameStart == GNUChessGame) {
8451             /* GNUChessGames have numbers, but they aren't move numbers */
8452             if (appData.debugMode)
8453               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8454                       yy_text, (int) moveType);
8455             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8456         }
8457         /* else fall thru */
8458
8459       case XBoardGame:
8460       case GNUChessGame:
8461       case PGNTag:
8462         /* Reached start of next game in file */
8463         if (appData.debugMode)
8464           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8465         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8466                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8467           case MT_NONE:
8468           case MT_CHECK:
8469             break;
8470           case MT_CHECKMATE:
8471           case MT_STAINMATE:
8472             if (WhiteOnMove(currentMove)) {
8473                 GameEnds(BlackWins, "Black mates", GE_FILE);
8474             } else {
8475                 GameEnds(WhiteWins, "White mates", GE_FILE);
8476             }
8477             break;
8478           case MT_STALEMATE:
8479             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8480             break;
8481         }
8482         done = TRUE;
8483         break;
8484
8485       case PositionDiagram:     /* should not happen; ignore */
8486       case ElapsedTime:         /* ignore */
8487       case NAG:                 /* ignore */
8488         if (appData.debugMode)
8489           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8490                   yy_text, (int) moveType);
8491         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8492
8493       case IllegalMove:
8494         if (appData.testLegality) {
8495             if (appData.debugMode)
8496               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8497             sprintf(move, _("Illegal move: %d.%s%s"),
8498                     (forwardMostMove / 2) + 1,
8499                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8500             DisplayError(move, 0);
8501             done = TRUE;
8502         } else {
8503             if (appData.debugMode)
8504               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8505                       yy_text, currentMoveString);
8506             fromX = currentMoveString[0] - AAA;
8507             fromY = currentMoveString[1] - ONE;
8508             toX = currentMoveString[2] - AAA;
8509             toY = currentMoveString[3] - ONE;
8510             promoChar = currentMoveString[4];
8511         }
8512         break;
8513
8514       case AmbiguousMove:
8515         if (appData.debugMode)
8516           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8517         sprintf(move, _("Ambiguous move: %d.%s%s"),
8518                 (forwardMostMove / 2) + 1,
8519                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8520         DisplayError(move, 0);
8521         done = TRUE;
8522         break;
8523
8524       default:
8525       case ImpossibleMove:
8526         if (appData.debugMode)
8527           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8528         sprintf(move, _("Illegal move: %d.%s%s"),
8529                 (forwardMostMove / 2) + 1,
8530                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8531         DisplayError(move, 0);
8532         done = TRUE;
8533         break;
8534     }
8535
8536     if (done) {
8537         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8538             DrawPosition(FALSE, boards[currentMove]);
8539             DisplayBothClocks();
8540             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8541               DisplayComment(currentMove - 1, commentList[currentMove]);
8542         }
8543         (void) StopLoadGameTimer();
8544         gameFileFP = NULL;
8545         cmailOldMove = forwardMostMove;
8546         return FALSE;
8547     } else {
8548         /* currentMoveString is set as a side-effect of yylex */
8549         strcat(currentMoveString, "\n");
8550         strcpy(moveList[forwardMostMove], currentMoveString);
8551         
8552         thinkOutput[0] = NULLCHAR;
8553         MakeMove(fromX, fromY, toX, toY, promoChar);
8554         currentMove = forwardMostMove;
8555         return TRUE;
8556     }
8557 }
8558
8559 /* Load the nth game from the given file */
8560 int
8561 LoadGameFromFile(filename, n, title, useList)
8562      char *filename;
8563      int n;
8564      char *title;
8565      /*Boolean*/ int useList;
8566 {
8567     FILE *f;
8568     char buf[MSG_SIZ];
8569
8570     if (strcmp(filename, "-") == 0) {
8571         f = stdin;
8572         title = "stdin";
8573     } else {
8574         f = fopen(filename, "rb");
8575         if (f == NULL) {
8576           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8577             DisplayError(buf, errno);
8578             return FALSE;
8579         }
8580     }
8581     if (fseek(f, 0, 0) == -1) {
8582         /* f is not seekable; probably a pipe */
8583         useList = FALSE;
8584     }
8585     if (useList && n == 0) {
8586         int error = GameListBuild(f);
8587         if (error) {
8588             DisplayError(_("Cannot build game list"), error);
8589         } else if (!ListEmpty(&gameList) &&
8590                    ((ListGame *) gameList.tailPred)->number > 1) {
8591             GameListPopUp(f, title);
8592             return TRUE;
8593         }
8594         GameListDestroy();
8595         n = 1;
8596     }
8597     if (n == 0) n = 1;
8598     return LoadGame(f, n, title, FALSE);
8599 }
8600
8601
8602 void
8603 MakeRegisteredMove()
8604 {
8605     int fromX, fromY, toX, toY;
8606     char promoChar;
8607     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8608         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8609           case CMAIL_MOVE:
8610           case CMAIL_DRAW:
8611             if (appData.debugMode)
8612               fprintf(debugFP, "Restoring %s for game %d\n",
8613                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8614     
8615             thinkOutput[0] = NULLCHAR;
8616             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8617             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8618             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8619             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8620             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8621             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8622             MakeMove(fromX, fromY, toX, toY, promoChar);
8623             ShowMove(fromX, fromY, toX, toY);
8624               
8625             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8626                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8627               case MT_NONE:
8628               case MT_CHECK:
8629                 break;
8630                 
8631               case MT_CHECKMATE:
8632               case MT_STAINMATE:
8633                 if (WhiteOnMove(currentMove)) {
8634                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8635                 } else {
8636                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8637                 }
8638                 break;
8639                 
8640               case MT_STALEMATE:
8641                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8642                 break;
8643             }
8644
8645             break;
8646             
8647           case CMAIL_RESIGN:
8648             if (WhiteOnMove(currentMove)) {
8649                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8650             } else {
8651                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8652             }
8653             break;
8654             
8655           case CMAIL_ACCEPT:
8656             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8657             break;
8658               
8659           default:
8660             break;
8661         }
8662     }
8663
8664     return;
8665 }
8666
8667 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8668 int
8669 CmailLoadGame(f, gameNumber, title, useList)
8670      FILE *f;
8671      int gameNumber;
8672      char *title;
8673      int useList;
8674 {
8675     int retVal;
8676
8677     if (gameNumber > nCmailGames) {
8678         DisplayError(_("No more games in this message"), 0);
8679         return FALSE;
8680     }
8681     if (f == lastLoadGameFP) {
8682         int offset = gameNumber - lastLoadGameNumber;
8683         if (offset == 0) {
8684             cmailMsg[0] = NULLCHAR;
8685             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8686                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8687                 nCmailMovesRegistered--;
8688             }
8689             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8690             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8691                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8692             }
8693         } else {
8694             if (! RegisterMove()) return FALSE;
8695         }
8696     }
8697
8698     retVal = LoadGame(f, gameNumber, title, useList);
8699
8700     /* Make move registered during previous look at this game, if any */
8701     MakeRegisteredMove();
8702
8703     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8704         commentList[currentMove]
8705           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8706         DisplayComment(currentMove - 1, commentList[currentMove]);
8707     }
8708
8709     return retVal;
8710 }
8711
8712 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8713 int
8714 ReloadGame(offset)
8715      int offset;
8716 {
8717     int gameNumber = lastLoadGameNumber + offset;
8718     if (lastLoadGameFP == NULL) {
8719         DisplayError(_("No game has been loaded yet"), 0);
8720         return FALSE;
8721     }
8722     if (gameNumber <= 0) {
8723         DisplayError(_("Can't back up any further"), 0);
8724         return FALSE;
8725     }
8726     if (cmailMsgLoaded) {
8727         return CmailLoadGame(lastLoadGameFP, gameNumber,
8728                              lastLoadGameTitle, lastLoadGameUseList);
8729     } else {
8730         return LoadGame(lastLoadGameFP, gameNumber,
8731                         lastLoadGameTitle, lastLoadGameUseList);
8732     }
8733 }
8734
8735
8736
8737 /* Load the nth game from open file f */
8738 int
8739 LoadGame(f, gameNumber, title, useList)
8740      FILE *f;
8741      int gameNumber;
8742      char *title;
8743      int useList;
8744 {
8745     ChessMove cm;
8746     char buf[MSG_SIZ];
8747     int gn = gameNumber;
8748     ListGame *lg = NULL;
8749     int numPGNTags = 0;
8750     int err;
8751     GameMode oldGameMode;
8752     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8753
8754     if (appData.debugMode) 
8755         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8756
8757     if (gameMode == Training )
8758         SetTrainingModeOff();
8759
8760     oldGameMode = gameMode;
8761     if (gameMode != BeginningOfGame) {
8762       Reset(FALSE, TRUE);
8763     }
8764
8765     gameFileFP = f;
8766     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8767         fclose(lastLoadGameFP);
8768     }
8769
8770     if (useList) {
8771         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8772         
8773         if (lg) {
8774             fseek(f, lg->offset, 0);
8775             GameListHighlight(gameNumber);
8776             gn = 1;
8777         }
8778         else {
8779             DisplayError(_("Game number out of range"), 0);
8780             return FALSE;
8781         }
8782     } else {
8783         GameListDestroy();
8784         if (fseek(f, 0, 0) == -1) {
8785             if (f == lastLoadGameFP ?
8786                 gameNumber == lastLoadGameNumber + 1 :
8787                 gameNumber == 1) {
8788                 gn = 1;
8789             } else {
8790                 DisplayError(_("Can't seek on game file"), 0);
8791                 return FALSE;
8792             }
8793         }
8794     }
8795     lastLoadGameFP = f;
8796     lastLoadGameNumber = gameNumber;
8797     strcpy(lastLoadGameTitle, title);
8798     lastLoadGameUseList = useList;
8799
8800     yynewfile(f);
8801
8802     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8803       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8804                 lg->gameInfo.black);
8805             DisplayTitle(buf);
8806     } else if (*title != NULLCHAR) {
8807         if (gameNumber > 1) {
8808             sprintf(buf, "%s %d", title, gameNumber);
8809             DisplayTitle(buf);
8810         } else {
8811             DisplayTitle(title);
8812         }
8813     }
8814
8815     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8816         gameMode = PlayFromGameFile;
8817         ModeHighlight();
8818     }
8819
8820     currentMove = forwardMostMove = backwardMostMove = 0;
8821     CopyBoard(boards[0], initialPosition);
8822     StopClocks();
8823
8824     /*
8825      * Skip the first gn-1 games in the file.
8826      * Also skip over anything that precedes an identifiable 
8827      * start of game marker, to avoid being confused by 
8828      * garbage at the start of the file.  Currently 
8829      * recognized start of game markers are the move number "1",
8830      * the pattern "gnuchess .* game", the pattern
8831      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8832      * A game that starts with one of the latter two patterns
8833      * will also have a move number 1, possibly
8834      * following a position diagram.
8835      * 5-4-02: Let's try being more lenient and allowing a game to
8836      * start with an unnumbered move.  Does that break anything?
8837      */
8838     cm = lastLoadGameStart = (ChessMove) 0;
8839     while (gn > 0) {
8840         yyboardindex = forwardMostMove;
8841         cm = (ChessMove) yylex();
8842         switch (cm) {
8843           case (ChessMove) 0:
8844             if (cmailMsgLoaded) {
8845                 nCmailGames = CMAIL_MAX_GAMES - gn;
8846             } else {
8847                 Reset(TRUE, TRUE);
8848                 DisplayError(_("Game not found in file"), 0);
8849             }
8850             return FALSE;
8851
8852           case GNUChessGame:
8853           case XBoardGame:
8854             gn--;
8855             lastLoadGameStart = cm;
8856             break;
8857             
8858           case MoveNumberOne:
8859             switch (lastLoadGameStart) {
8860               case GNUChessGame:
8861               case XBoardGame:
8862               case PGNTag:
8863                 break;
8864               case MoveNumberOne:
8865               case (ChessMove) 0:
8866                 gn--;           /* count this game */
8867                 lastLoadGameStart = cm;
8868                 break;
8869               default:
8870                 /* impossible */
8871                 break;
8872             }
8873             break;
8874
8875           case PGNTag:
8876             switch (lastLoadGameStart) {
8877               case GNUChessGame:
8878               case PGNTag:
8879               case MoveNumberOne:
8880               case (ChessMove) 0:
8881                 gn--;           /* count this game */
8882                 lastLoadGameStart = cm;
8883                 break;
8884               case XBoardGame:
8885                 lastLoadGameStart = cm; /* game counted already */
8886                 break;
8887               default:
8888                 /* impossible */
8889                 break;
8890             }
8891             if (gn > 0) {
8892                 do {
8893                     yyboardindex = forwardMostMove;
8894                     cm = (ChessMove) yylex();
8895                 } while (cm == PGNTag || cm == Comment);
8896             }
8897             break;
8898
8899           case WhiteWins:
8900           case BlackWins:
8901           case GameIsDrawn:
8902             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8903                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8904                     != CMAIL_OLD_RESULT) {
8905                     nCmailResults ++ ;
8906                     cmailResult[  CMAIL_MAX_GAMES
8907                                 - gn - 1] = CMAIL_OLD_RESULT;
8908                 }
8909             }
8910             break;
8911
8912           case NormalMove:
8913             /* Only a NormalMove can be at the start of a game
8914              * without a position diagram. */
8915             if (lastLoadGameStart == (ChessMove) 0) {
8916               gn--;
8917               lastLoadGameStart = MoveNumberOne;
8918             }
8919             break;
8920
8921           default:
8922             break;
8923         }
8924     }
8925     
8926     if (appData.debugMode)
8927       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8928
8929     if (cm == XBoardGame) {
8930         /* Skip any header junk before position diagram and/or move 1 */
8931         for (;;) {
8932             yyboardindex = forwardMostMove;
8933             cm = (ChessMove) yylex();
8934
8935             if (cm == (ChessMove) 0 ||
8936                 cm == GNUChessGame || cm == XBoardGame) {
8937                 /* Empty game; pretend end-of-file and handle later */
8938                 cm = (ChessMove) 0;
8939                 break;
8940             }
8941
8942             if (cm == MoveNumberOne || cm == PositionDiagram ||
8943                 cm == PGNTag || cm == Comment)
8944               break;
8945         }
8946     } else if (cm == GNUChessGame) {
8947         if (gameInfo.event != NULL) {
8948             free(gameInfo.event);
8949         }
8950         gameInfo.event = StrSave(yy_text);
8951     }   
8952
8953     startedFromSetupPosition = FALSE;
8954     while (cm == PGNTag) {
8955         if (appData.debugMode) 
8956           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8957         err = ParsePGNTag(yy_text, &gameInfo);
8958         if (!err) numPGNTags++;
8959
8960         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8961         if(gameInfo.variant != oldVariant) {
8962             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8963             InitPosition(TRUE);
8964             oldVariant = gameInfo.variant;
8965             if (appData.debugMode) 
8966               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8967         }
8968
8969
8970         if (gameInfo.fen != NULL) {
8971           Board initial_position;
8972           startedFromSetupPosition = TRUE;
8973           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8974             Reset(TRUE, TRUE);
8975             DisplayError(_("Bad FEN position in file"), 0);
8976             return FALSE;
8977           }
8978           CopyBoard(boards[0], initial_position);
8979           if (blackPlaysFirst) {
8980             currentMove = forwardMostMove = backwardMostMove = 1;
8981             CopyBoard(boards[1], initial_position);
8982             strcpy(moveList[0], "");
8983             strcpy(parseList[0], "");
8984             timeRemaining[0][1] = whiteTimeRemaining;
8985             timeRemaining[1][1] = blackTimeRemaining;
8986             if (commentList[0] != NULL) {
8987               commentList[1] = commentList[0];
8988               commentList[0] = NULL;
8989             }
8990           } else {
8991             currentMove = forwardMostMove = backwardMostMove = 0;
8992           }
8993           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8994           {   int i;
8995               initialRulePlies = FENrulePlies;
8996               epStatus[forwardMostMove] = FENepStatus;
8997               for( i=0; i< nrCastlingRights; i++ )
8998                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8999           }
9000           yyboardindex = forwardMostMove;
9001           free(gameInfo.fen);
9002           gameInfo.fen = NULL;
9003         }
9004
9005         yyboardindex = forwardMostMove;
9006         cm = (ChessMove) yylex();
9007
9008         /* Handle comments interspersed among the tags */
9009         while (cm == Comment) {
9010             char *p;
9011             if (appData.debugMode) 
9012               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9013             p = yy_text;
9014             if (*p == '{' || *p == '[' || *p == '(') {
9015                 p[strlen(p) - 1] = NULLCHAR;
9016                 p++;
9017             }
9018             while (*p == '\n') p++;
9019             AppendComment(currentMove, p);
9020             yyboardindex = forwardMostMove;
9021             cm = (ChessMove) yylex();
9022         }
9023     }
9024
9025     /* don't rely on existence of Event tag since if game was
9026      * pasted from clipboard the Event tag may not exist
9027      */
9028     if (numPGNTags > 0){
9029         char *tags;
9030         if (gameInfo.variant == VariantNormal) {
9031           gameInfo.variant = StringToVariant(gameInfo.event);
9032         }
9033         if (!matchMode) {
9034           if( appData.autoDisplayTags ) {
9035             tags = PGNTags(&gameInfo);
9036             TagsPopUp(tags, CmailMsg());
9037             free(tags);
9038           }
9039         }
9040     } else {
9041         /* Make something up, but don't display it now */
9042         SetGameInfo();
9043         TagsPopDown();
9044     }
9045
9046     if (cm == PositionDiagram) {
9047         int i, j;
9048         char *p;
9049         Board initial_position;
9050
9051         if (appData.debugMode)
9052           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9053
9054         if (!startedFromSetupPosition) {
9055             p = yy_text;
9056             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9057               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9058                 switch (*p) {
9059                   case '[':
9060                   case '-':
9061                   case ' ':
9062                   case '\t':
9063                   case '\n':
9064                   case '\r':
9065                     break;
9066                   default:
9067                     initial_position[i][j++] = CharToPiece(*p);
9068                     break;
9069                 }
9070             while (*p == ' ' || *p == '\t' ||
9071                    *p == '\n' || *p == '\r') p++;
9072         
9073             if (strncmp(p, "black", strlen("black"))==0)
9074               blackPlaysFirst = TRUE;
9075             else
9076               blackPlaysFirst = FALSE;
9077             startedFromSetupPosition = TRUE;
9078         
9079             CopyBoard(boards[0], initial_position);
9080             if (blackPlaysFirst) {
9081                 currentMove = forwardMostMove = backwardMostMove = 1;
9082                 CopyBoard(boards[1], initial_position);
9083                 strcpy(moveList[0], "");
9084                 strcpy(parseList[0], "");
9085                 timeRemaining[0][1] = whiteTimeRemaining;
9086                 timeRemaining[1][1] = blackTimeRemaining;
9087                 if (commentList[0] != NULL) {
9088                     commentList[1] = commentList[0];
9089                     commentList[0] = NULL;
9090                 }
9091             } else {
9092                 currentMove = forwardMostMove = backwardMostMove = 0;
9093             }
9094         }
9095         yyboardindex = forwardMostMove;
9096         cm = (ChessMove) yylex();
9097     }
9098
9099     if (first.pr == NoProc) {
9100         StartChessProgram(&first);
9101     }
9102     InitChessProgram(&first, FALSE);
9103     SendToProgram("force\n", &first);
9104     if (startedFromSetupPosition) {
9105         SendBoard(&first, forwardMostMove);
9106     if (appData.debugMode) {
9107         fprintf(debugFP, "Load Game\n");
9108     }
9109         DisplayBothClocks();
9110     }      
9111
9112     /* [HGM] server: flag to write setup moves in broadcast file as one */
9113     loadFlag = appData.suppressLoadMoves;
9114
9115     while (cm == Comment) {
9116         char *p;
9117         if (appData.debugMode) 
9118           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9119         p = yy_text;
9120         if (*p == '{' || *p == '[' || *p == '(') {
9121             p[strlen(p) - 1] = NULLCHAR;
9122             p++;
9123         }
9124         while (*p == '\n') p++;
9125         AppendComment(currentMove, p);
9126         yyboardindex = forwardMostMove;
9127         cm = (ChessMove) yylex();
9128     }
9129
9130     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9131         cm == WhiteWins || cm == BlackWins ||
9132         cm == GameIsDrawn || cm == GameUnfinished) {
9133         DisplayMessage("", _("No moves in game"));
9134         if (cmailMsgLoaded) {
9135             if (appData.debugMode)
9136               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9137             ClearHighlights();
9138             flipView = FALSE;
9139         }
9140         DrawPosition(FALSE, boards[currentMove]);
9141         DisplayBothClocks();
9142         gameMode = EditGame;
9143         ModeHighlight();
9144         gameFileFP = NULL;
9145         cmailOldMove = 0;
9146         return TRUE;
9147     }
9148
9149     // [HGM] PV info: routine tests if comment empty
9150     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9151         DisplayComment(currentMove - 1, commentList[currentMove]);
9152     }
9153     if (!matchMode && appData.timeDelay != 0) 
9154       DrawPosition(FALSE, boards[currentMove]);
9155
9156     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9157       programStats.ok_to_send = 1;
9158     }
9159
9160     /* if the first token after the PGN tags is a move
9161      * and not move number 1, retrieve it from the parser 
9162      */
9163     if (cm != MoveNumberOne)
9164         LoadGameOneMove(cm);
9165
9166     /* load the remaining moves from the file */
9167     while (LoadGameOneMove((ChessMove)0)) {
9168       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9169       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9170     }
9171
9172     /* rewind to the start of the game */
9173     currentMove = backwardMostMove;
9174
9175     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9176
9177     if (oldGameMode == AnalyzeFile ||
9178         oldGameMode == AnalyzeMode) {
9179       AnalyzeFileEvent();
9180     }
9181
9182     if (matchMode || appData.timeDelay == 0) {
9183       ToEndEvent();
9184       gameMode = EditGame;
9185       ModeHighlight();
9186     } else if (appData.timeDelay > 0) {
9187       AutoPlayGameLoop();
9188     }
9189
9190     if (appData.debugMode) 
9191         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9192
9193     loadFlag = 0; /* [HGM] true game starts */
9194     return TRUE;
9195 }
9196
9197 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9198 int
9199 ReloadPosition(offset)
9200      int offset;
9201 {
9202     int positionNumber = lastLoadPositionNumber + offset;
9203     if (lastLoadPositionFP == NULL) {
9204         DisplayError(_("No position has been loaded yet"), 0);
9205         return FALSE;
9206     }
9207     if (positionNumber <= 0) {
9208         DisplayError(_("Can't back up any further"), 0);
9209         return FALSE;
9210     }
9211     return LoadPosition(lastLoadPositionFP, positionNumber,
9212                         lastLoadPositionTitle);
9213 }
9214
9215 /* Load the nth position from the given file */
9216 int
9217 LoadPositionFromFile(filename, n, title)
9218      char *filename;
9219      int n;
9220      char *title;
9221 {
9222     FILE *f;
9223     char buf[MSG_SIZ];
9224
9225     if (strcmp(filename, "-") == 0) {
9226         return LoadPosition(stdin, n, "stdin");
9227     } else {
9228         f = fopen(filename, "rb");
9229         if (f == NULL) {
9230             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9231             DisplayError(buf, errno);
9232             return FALSE;
9233         } else {
9234             return LoadPosition(f, n, title);
9235         }
9236     }
9237 }
9238
9239 /* Load the nth position from the given open file, and close it */
9240 int
9241 LoadPosition(f, positionNumber, title)
9242      FILE *f;
9243      int positionNumber;
9244      char *title;
9245 {
9246     char *p, line[MSG_SIZ];
9247     Board initial_position;
9248     int i, j, fenMode, pn;
9249     
9250     if (gameMode == Training )
9251         SetTrainingModeOff();
9252
9253     if (gameMode != BeginningOfGame) {
9254         Reset(FALSE, TRUE);
9255     }
9256     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9257         fclose(lastLoadPositionFP);
9258     }
9259     if (positionNumber == 0) positionNumber = 1;
9260     lastLoadPositionFP = f;
9261     lastLoadPositionNumber = positionNumber;
9262     strcpy(lastLoadPositionTitle, title);
9263     if (first.pr == NoProc) {
9264       StartChessProgram(&first);
9265       InitChessProgram(&first, FALSE);
9266     }    
9267     pn = positionNumber;
9268     if (positionNumber < 0) {
9269         /* Negative position number means to seek to that byte offset */
9270         if (fseek(f, -positionNumber, 0) == -1) {
9271             DisplayError(_("Can't seek on position file"), 0);
9272             return FALSE;
9273         };
9274         pn = 1;
9275     } else {
9276         if (fseek(f, 0, 0) == -1) {
9277             if (f == lastLoadPositionFP ?
9278                 positionNumber == lastLoadPositionNumber + 1 :
9279                 positionNumber == 1) {
9280                 pn = 1;
9281             } else {
9282                 DisplayError(_("Can't seek on position file"), 0);
9283                 return FALSE;
9284             }
9285         }
9286     }
9287     /* See if this file is FEN or old-style xboard */
9288     if (fgets(line, MSG_SIZ, f) == NULL) {
9289         DisplayError(_("Position not found in file"), 0);
9290         return FALSE;
9291     }
9292     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9293     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9294
9295     if (pn >= 2) {
9296         if (fenMode || line[0] == '#') pn--;
9297         while (pn > 0) {
9298             /* skip positions before number pn */
9299             if (fgets(line, MSG_SIZ, f) == NULL) {
9300                 Reset(TRUE, TRUE);
9301                 DisplayError(_("Position not found in file"), 0);
9302                 return FALSE;
9303             }
9304             if (fenMode || line[0] == '#') pn--;
9305         }
9306     }
9307
9308     if (fenMode) {
9309         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9310             DisplayError(_("Bad FEN position in file"), 0);
9311             return FALSE;
9312         }
9313     } else {
9314         (void) fgets(line, MSG_SIZ, f);
9315         (void) fgets(line, MSG_SIZ, f);
9316     
9317         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9318             (void) fgets(line, MSG_SIZ, f);
9319             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9320                 if (*p == ' ')
9321                   continue;
9322                 initial_position[i][j++] = CharToPiece(*p);
9323             }
9324         }
9325     
9326         blackPlaysFirst = FALSE;
9327         if (!feof(f)) {
9328             (void) fgets(line, MSG_SIZ, f);
9329             if (strncmp(line, "black", strlen("black"))==0)
9330               blackPlaysFirst = TRUE;
9331         }
9332     }
9333     startedFromSetupPosition = TRUE;
9334     
9335     SendToProgram("force\n", &first);
9336     CopyBoard(boards[0], initial_position);
9337     if (blackPlaysFirst) {
9338         currentMove = forwardMostMove = backwardMostMove = 1;
9339         strcpy(moveList[0], "");
9340         strcpy(parseList[0], "");
9341         CopyBoard(boards[1], initial_position);
9342         DisplayMessage("", _("Black to play"));
9343     } else {
9344         currentMove = forwardMostMove = backwardMostMove = 0;
9345         DisplayMessage("", _("White to play"));
9346     }
9347           /* [HGM] copy FEN attributes as well */
9348           {   int i;
9349               initialRulePlies = FENrulePlies;
9350               epStatus[forwardMostMove] = FENepStatus;
9351               for( i=0; i< nrCastlingRights; i++ )
9352                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9353           }
9354     SendBoard(&first, forwardMostMove);
9355     if (appData.debugMode) {
9356 int i, j;
9357   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9358   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9359         fprintf(debugFP, "Load Position\n");
9360     }
9361
9362     if (positionNumber > 1) {
9363         sprintf(line, "%s %d", title, positionNumber);
9364         DisplayTitle(line);
9365     } else {
9366         DisplayTitle(title);
9367     }
9368     gameMode = EditGame;
9369     ModeHighlight();
9370     ResetClocks();
9371     timeRemaining[0][1] = whiteTimeRemaining;
9372     timeRemaining[1][1] = blackTimeRemaining;
9373     DrawPosition(FALSE, boards[currentMove]);
9374    
9375     return TRUE;
9376 }
9377
9378
9379 void
9380 CopyPlayerNameIntoFileName(dest, src)
9381      char **dest, *src;
9382 {
9383     while (*src != NULLCHAR && *src != ',') {
9384         if (*src == ' ') {
9385             *(*dest)++ = '_';
9386             src++;
9387         } else {
9388             *(*dest)++ = *src++;
9389         }
9390     }
9391 }
9392
9393 char *DefaultFileName(ext)
9394      char *ext;
9395 {
9396     static char def[MSG_SIZ];
9397     char *p;
9398
9399     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9400         p = def;
9401         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9402         *p++ = '-';
9403         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9404         *p++ = '.';
9405         strcpy(p, ext);
9406     } else {
9407         def[0] = NULLCHAR;
9408     }
9409     return def;
9410 }
9411
9412 /* Save the current game to the given file */
9413 int
9414 SaveGameToFile(filename, append)
9415      char *filename;
9416      int append;
9417 {
9418     FILE *f;
9419     char buf[MSG_SIZ];
9420
9421     if (strcmp(filename, "-") == 0) {
9422         return SaveGame(stdout, 0, NULL);
9423     } else {
9424         f = fopen(filename, append ? "a" : "w");
9425         if (f == NULL) {
9426             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9427             DisplayError(buf, errno);
9428             return FALSE;
9429         } else {
9430             return SaveGame(f, 0, NULL);
9431         }
9432     }
9433 }
9434
9435 char *
9436 SavePart(str)
9437      char *str;
9438 {
9439     static char buf[MSG_SIZ];
9440     char *p;
9441     
9442     p = strchr(str, ' ');
9443     if (p == NULL) return str;
9444     strncpy(buf, str, p - str);
9445     buf[p - str] = NULLCHAR;
9446     return buf;
9447 }
9448
9449 #define PGN_MAX_LINE 75
9450
9451 #define PGN_SIDE_WHITE  0
9452 #define PGN_SIDE_BLACK  1
9453
9454 /* [AS] */
9455 static int FindFirstMoveOutOfBook( int side )
9456 {
9457     int result = -1;
9458
9459     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9460         int index = backwardMostMove;
9461         int has_book_hit = 0;
9462
9463         if( (index % 2) != side ) {
9464             index++;
9465         }
9466
9467         while( index < forwardMostMove ) {
9468             /* Check to see if engine is in book */
9469             int depth = pvInfoList[index].depth;
9470             int score = pvInfoList[index].score;
9471             int in_book = 0;
9472
9473             if( depth <= 2 ) {
9474                 in_book = 1;
9475             }
9476             else if( score == 0 && depth == 63 ) {
9477                 in_book = 1; /* Zappa */
9478             }
9479             else if( score == 2 && depth == 99 ) {
9480                 in_book = 1; /* Abrok */
9481             }
9482
9483             has_book_hit += in_book;
9484
9485             if( ! in_book ) {
9486                 result = index;
9487
9488                 break;
9489             }
9490
9491             index += 2;
9492         }
9493     }
9494
9495     return result;
9496 }
9497
9498 /* [AS] */
9499 void GetOutOfBookInfo( char * buf )
9500 {
9501     int oob[2];
9502     int i;
9503     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9504
9505     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9506     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9507
9508     *buf = '\0';
9509
9510     if( oob[0] >= 0 || oob[1] >= 0 ) {
9511         for( i=0; i<2; i++ ) {
9512             int idx = oob[i];
9513
9514             if( idx >= 0 ) {
9515                 if( i > 0 && oob[0] >= 0 ) {
9516                     strcat( buf, "   " );
9517                 }
9518
9519                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9520                 sprintf( buf+strlen(buf), "%s%.2f", 
9521                     pvInfoList[idx].score >= 0 ? "+" : "",
9522                     pvInfoList[idx].score / 100.0 );
9523             }
9524         }
9525     }
9526 }
9527
9528 /* Save game in PGN style and close the file */
9529 int
9530 SaveGamePGN(f)
9531      FILE *f;
9532 {
9533     int i, offset, linelen, newblock;
9534     time_t tm;
9535 //    char *movetext;
9536     char numtext[32];
9537     int movelen, numlen, blank;
9538     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9539
9540     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9541     
9542     tm = time((time_t *) NULL);
9543     
9544     PrintPGNTags(f, &gameInfo);
9545     
9546     if (backwardMostMove > 0 || startedFromSetupPosition) {
9547         char *fen = PositionToFEN(backwardMostMove, NULL);
9548         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9549         fprintf(f, "\n{--------------\n");
9550         PrintPosition(f, backwardMostMove);
9551         fprintf(f, "--------------}\n");
9552         free(fen);
9553     }
9554     else {
9555         /* [AS] Out of book annotation */
9556         if( appData.saveOutOfBookInfo ) {
9557             char buf[64];
9558
9559             GetOutOfBookInfo( buf );
9560
9561             if( buf[0] != '\0' ) {
9562                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9563             }
9564         }
9565
9566         fprintf(f, "\n");
9567     }
9568
9569     i = backwardMostMove;
9570     linelen = 0;
9571     newblock = TRUE;
9572
9573     while (i < forwardMostMove) {
9574         /* Print comments preceding this move */
9575         if (commentList[i] != NULL) {
9576             if (linelen > 0) fprintf(f, "\n");
9577             fprintf(f, "{\n%s}\n", commentList[i]);
9578             linelen = 0;
9579             newblock = TRUE;
9580         }
9581
9582         /* Format move number */
9583         if ((i % 2) == 0) {
9584             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9585         } else {
9586             if (newblock) {
9587                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9588             } else {
9589                 numtext[0] = NULLCHAR;
9590             }
9591         }
9592         numlen = strlen(numtext);
9593         newblock = FALSE;
9594
9595         /* Print move number */
9596         blank = linelen > 0 && numlen > 0;
9597         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9598             fprintf(f, "\n");
9599             linelen = 0;
9600             blank = 0;
9601         }
9602         if (blank) {
9603             fprintf(f, " ");
9604             linelen++;
9605         }
9606         fprintf(f, "%s", numtext);
9607         linelen += numlen;
9608
9609         /* Get move */
9610         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9611         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9612
9613         /* Print move */
9614         blank = linelen > 0 && movelen > 0;
9615         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9616             fprintf(f, "\n");
9617             linelen = 0;
9618             blank = 0;
9619         }
9620         if (blank) {
9621             fprintf(f, " ");
9622             linelen++;
9623         }
9624         fprintf(f, "%s", move_buffer);
9625         linelen += movelen;
9626
9627         /* [AS] Add PV info if present */
9628         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9629             /* [HGM] add time */
9630             char buf[MSG_SIZ]; int seconds = 0;
9631
9632             if(i >= backwardMostMove) {
9633                 if(WhiteOnMove(i))
9634                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9635                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9636                 else
9637                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9638                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9639             }
9640             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9641
9642             if( seconds <= 0) buf[0] = 0; else
9643             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9644                 seconds = (seconds + 4)/10; // round to full seconds
9645                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9646                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9647             }
9648
9649             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9650                 pvInfoList[i].score >= 0 ? "+" : "",
9651                 pvInfoList[i].score / 100.0,
9652                 pvInfoList[i].depth,
9653                 buf );
9654
9655             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9656
9657             /* Print score/depth */
9658             blank = linelen > 0 && movelen > 0;
9659             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9660                 fprintf(f, "\n");
9661                 linelen = 0;
9662                 blank = 0;
9663             }
9664             if (blank) {
9665                 fprintf(f, " ");
9666                 linelen++;
9667             }
9668             fprintf(f, "%s", move_buffer);
9669             linelen += movelen;
9670         }
9671
9672         i++;
9673     }
9674     
9675     /* Start a new line */
9676     if (linelen > 0) fprintf(f, "\n");
9677
9678     /* Print comments after last move */
9679     if (commentList[i] != NULL) {
9680         fprintf(f, "{\n%s}\n", commentList[i]);
9681     }
9682
9683     /* Print result */
9684     if (gameInfo.resultDetails != NULL &&
9685         gameInfo.resultDetails[0] != NULLCHAR) {
9686         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9687                 PGNResult(gameInfo.result));
9688     } else {
9689         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9690     }
9691
9692     fclose(f);
9693     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9694     return TRUE;
9695 }
9696
9697 /* Save game in old style and close the file */
9698 int
9699 SaveGameOldStyle(f)
9700      FILE *f;
9701 {
9702     int i, offset;
9703     time_t tm;
9704     
9705     tm = time((time_t *) NULL);
9706     
9707     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9708     PrintOpponents(f);
9709     
9710     if (backwardMostMove > 0 || startedFromSetupPosition) {
9711         fprintf(f, "\n[--------------\n");
9712         PrintPosition(f, backwardMostMove);
9713         fprintf(f, "--------------]\n");
9714     } else {
9715         fprintf(f, "\n");
9716     }
9717
9718     i = backwardMostMove;
9719     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9720
9721     while (i < forwardMostMove) {
9722         if (commentList[i] != NULL) {
9723             fprintf(f, "[%s]\n", commentList[i]);
9724         }
9725
9726         if ((i % 2) == 1) {
9727             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9728             i++;
9729         } else {
9730             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9731             i++;
9732             if (commentList[i] != NULL) {
9733                 fprintf(f, "\n");
9734                 continue;
9735             }
9736             if (i >= forwardMostMove) {
9737                 fprintf(f, "\n");
9738                 break;
9739             }
9740             fprintf(f, "%s\n", parseList[i]);
9741             i++;
9742         }
9743     }
9744     
9745     if (commentList[i] != NULL) {
9746         fprintf(f, "[%s]\n", commentList[i]);
9747     }
9748
9749     /* This isn't really the old style, but it's close enough */
9750     if (gameInfo.resultDetails != NULL &&
9751         gameInfo.resultDetails[0] != NULLCHAR) {
9752         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9753                 gameInfo.resultDetails);
9754     } else {
9755         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9756     }
9757
9758     fclose(f);
9759     return TRUE;
9760 }
9761
9762 /* Save the current game to open file f and close the file */
9763 int
9764 SaveGame(f, dummy, dummy2)
9765      FILE *f;
9766      int dummy;
9767      char *dummy2;
9768 {
9769     if (gameMode == EditPosition) EditPositionDone();
9770     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9771     if (appData.oldSaveStyle)
9772       return SaveGameOldStyle(f);
9773     else
9774       return SaveGamePGN(f);
9775 }
9776
9777 /* Save the current position to the given file */
9778 int
9779 SavePositionToFile(filename)
9780      char *filename;
9781 {
9782     FILE *f;
9783     char buf[MSG_SIZ];
9784
9785     if (strcmp(filename, "-") == 0) {
9786         return SavePosition(stdout, 0, NULL);
9787     } else {
9788         f = fopen(filename, "a");
9789         if (f == NULL) {
9790             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9791             DisplayError(buf, errno);
9792             return FALSE;
9793         } else {
9794             SavePosition(f, 0, NULL);
9795             return TRUE;
9796         }
9797     }
9798 }
9799
9800 /* Save the current position to the given open file and close the file */
9801 int
9802 SavePosition(f, dummy, dummy2)
9803      FILE *f;
9804      int dummy;
9805      char *dummy2;
9806 {
9807     time_t tm;
9808     char *fen;
9809     
9810     if (appData.oldSaveStyle) {
9811         tm = time((time_t *) NULL);
9812     
9813         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9814         PrintOpponents(f);
9815         fprintf(f, "[--------------\n");
9816         PrintPosition(f, currentMove);
9817         fprintf(f, "--------------]\n");
9818     } else {
9819         fen = PositionToFEN(currentMove, NULL);
9820         fprintf(f, "%s\n", fen);
9821         free(fen);
9822     }
9823     fclose(f);
9824     return TRUE;
9825 }
9826
9827 void
9828 ReloadCmailMsgEvent(unregister)
9829      int unregister;
9830 {
9831 #if !WIN32
9832     static char *inFilename = NULL;
9833     static char *outFilename;
9834     int i;
9835     struct stat inbuf, outbuf;
9836     int status;
9837     
9838     /* Any registered moves are unregistered if unregister is set, */
9839     /* i.e. invoked by the signal handler */
9840     if (unregister) {
9841         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9842             cmailMoveRegistered[i] = FALSE;
9843             if (cmailCommentList[i] != NULL) {
9844                 free(cmailCommentList[i]);
9845                 cmailCommentList[i] = NULL;
9846             }
9847         }
9848         nCmailMovesRegistered = 0;
9849     }
9850
9851     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9852         cmailResult[i] = CMAIL_NOT_RESULT;
9853     }
9854     nCmailResults = 0;
9855
9856     if (inFilename == NULL) {
9857         /* Because the filenames are static they only get malloced once  */
9858         /* and they never get freed                                      */
9859         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9860         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9861
9862         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9863         sprintf(outFilename, "%s.out", appData.cmailGameName);
9864     }
9865     
9866     status = stat(outFilename, &outbuf);
9867     if (status < 0) {
9868         cmailMailedMove = FALSE;
9869     } else {
9870         status = stat(inFilename, &inbuf);
9871         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9872     }
9873     
9874     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9875        counts the games, notes how each one terminated, etc.
9876        
9877        It would be nice to remove this kludge and instead gather all
9878        the information while building the game list.  (And to keep it
9879        in the game list nodes instead of having a bunch of fixed-size
9880        parallel arrays.)  Note this will require getting each game's
9881        termination from the PGN tags, as the game list builder does
9882        not process the game moves.  --mann
9883        */
9884     cmailMsgLoaded = TRUE;
9885     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9886     
9887     /* Load first game in the file or popup game menu */
9888     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9889
9890 #endif /* !WIN32 */
9891     return;
9892 }
9893
9894 int
9895 RegisterMove()
9896 {
9897     FILE *f;
9898     char string[MSG_SIZ];
9899
9900     if (   cmailMailedMove
9901         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9902         return TRUE;            /* Allow free viewing  */
9903     }
9904
9905     /* Unregister move to ensure that we don't leave RegisterMove        */
9906     /* with the move registered when the conditions for registering no   */
9907     /* longer hold                                                       */
9908     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9909         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9910         nCmailMovesRegistered --;
9911
9912         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9913           {
9914               free(cmailCommentList[lastLoadGameNumber - 1]);
9915               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9916           }
9917     }
9918
9919     if (cmailOldMove == -1) {
9920         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9921         return FALSE;
9922     }
9923
9924     if (currentMove > cmailOldMove + 1) {
9925         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9926         return FALSE;
9927     }
9928
9929     if (currentMove < cmailOldMove) {
9930         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9931         return FALSE;
9932     }
9933
9934     if (forwardMostMove > currentMove) {
9935         /* Silently truncate extra moves */
9936         TruncateGame();
9937     }
9938
9939     if (   (currentMove == cmailOldMove + 1)
9940         || (   (currentMove == cmailOldMove)
9941             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9942                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9943         if (gameInfo.result != GameUnfinished) {
9944             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9945         }
9946
9947         if (commentList[currentMove] != NULL) {
9948             cmailCommentList[lastLoadGameNumber - 1]
9949               = StrSave(commentList[currentMove]);
9950         }
9951         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9952
9953         if (appData.debugMode)
9954           fprintf(debugFP, "Saving %s for game %d\n",
9955                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9956
9957         sprintf(string,
9958                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9959         
9960         f = fopen(string, "w");
9961         if (appData.oldSaveStyle) {
9962             SaveGameOldStyle(f); /* also closes the file */
9963             
9964             sprintf(string, "%s.pos.out", appData.cmailGameName);
9965             f = fopen(string, "w");
9966             SavePosition(f, 0, NULL); /* also closes the file */
9967         } else {
9968             fprintf(f, "{--------------\n");
9969             PrintPosition(f, currentMove);
9970             fprintf(f, "--------------}\n\n");
9971             
9972             SaveGame(f, 0, NULL); /* also closes the file*/
9973         }
9974         
9975         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9976         nCmailMovesRegistered ++;
9977     } else if (nCmailGames == 1) {
9978         DisplayError(_("You have not made a move yet"), 0);
9979         return FALSE;
9980     }
9981
9982     return TRUE;
9983 }
9984
9985 void
9986 MailMoveEvent()
9987 {
9988 #if !WIN32
9989     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9990     FILE *commandOutput;
9991     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9992     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9993     int nBuffers;
9994     int i;
9995     int archived;
9996     char *arcDir;
9997
9998     if (! cmailMsgLoaded) {
9999         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10000         return;
10001     }
10002
10003     if (nCmailGames == nCmailResults) {
10004         DisplayError(_("No unfinished games"), 0);
10005         return;
10006     }
10007
10008 #if CMAIL_PROHIBIT_REMAIL
10009     if (cmailMailedMove) {
10010         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);
10011         DisplayError(msg, 0);
10012         return;
10013     }
10014 #endif
10015
10016     if (! (cmailMailedMove || RegisterMove())) return;
10017     
10018     if (   cmailMailedMove
10019         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10020         sprintf(string, partCommandString,
10021                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10022         commandOutput = popen(string, "r");
10023
10024         if (commandOutput == NULL) {
10025             DisplayError(_("Failed to invoke cmail"), 0);
10026         } else {
10027             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10028                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10029             }
10030             if (nBuffers > 1) {
10031                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10032                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10033                 nBytes = MSG_SIZ - 1;
10034             } else {
10035                 (void) memcpy(msg, buffer, nBytes);
10036             }
10037             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10038
10039             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10040                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10041
10042                 archived = TRUE;
10043                 for (i = 0; i < nCmailGames; i ++) {
10044                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10045                         archived = FALSE;
10046                     }
10047                 }
10048                 if (   archived
10049                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10050                         != NULL)) {
10051                     sprintf(buffer, "%s/%s.%s.archive",
10052                             arcDir,
10053                             appData.cmailGameName,
10054                             gameInfo.date);
10055                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10056                     cmailMsgLoaded = FALSE;
10057                 }
10058             }
10059
10060             DisplayInformation(msg);
10061             pclose(commandOutput);
10062         }
10063     } else {
10064         if ((*cmailMsg) != '\0') {
10065             DisplayInformation(cmailMsg);
10066         }
10067     }
10068
10069     return;
10070 #endif /* !WIN32 */
10071 }
10072
10073 char *
10074 CmailMsg()
10075 {
10076 #if WIN32
10077     return NULL;
10078 #else
10079     int  prependComma = 0;
10080     char number[5];
10081     char string[MSG_SIZ];       /* Space for game-list */
10082     int  i;
10083     
10084     if (!cmailMsgLoaded) return "";
10085
10086     if (cmailMailedMove) {
10087         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10088     } else {
10089         /* Create a list of games left */
10090         sprintf(string, "[");
10091         for (i = 0; i < nCmailGames; i ++) {
10092             if (! (   cmailMoveRegistered[i]
10093                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10094                 if (prependComma) {
10095                     sprintf(number, ",%d", i + 1);
10096                 } else {
10097                     sprintf(number, "%d", i + 1);
10098                     prependComma = 1;
10099                 }
10100                 
10101                 strcat(string, number);
10102             }
10103         }
10104         strcat(string, "]");
10105
10106         if (nCmailMovesRegistered + nCmailResults == 0) {
10107             switch (nCmailGames) {
10108               case 1:
10109                 sprintf(cmailMsg,
10110                         _("Still need to make move for game\n"));
10111                 break;
10112                 
10113               case 2:
10114                 sprintf(cmailMsg,
10115                         _("Still need to make moves for both games\n"));
10116                 break;
10117                 
10118               default:
10119                 sprintf(cmailMsg,
10120                         _("Still need to make moves for all %d games\n"),
10121                         nCmailGames);
10122                 break;
10123             }
10124         } else {
10125             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10126               case 1:
10127                 sprintf(cmailMsg,
10128                         _("Still need to make a move for game %s\n"),
10129                         string);
10130                 break;
10131                 
10132               case 0:
10133                 if (nCmailResults == nCmailGames) {
10134                     sprintf(cmailMsg, _("No unfinished games\n"));
10135                 } else {
10136                     sprintf(cmailMsg, _("Ready to send mail\n"));
10137                 }
10138                 break;
10139                 
10140               default:
10141                 sprintf(cmailMsg,
10142                         _("Still need to make moves for games %s\n"),
10143                         string);
10144             }
10145         }
10146     }
10147     return cmailMsg;
10148 #endif /* WIN32 */
10149 }
10150
10151 void
10152 ResetGameEvent()
10153 {
10154     if (gameMode == Training)
10155       SetTrainingModeOff();
10156
10157     Reset(TRUE, TRUE);
10158     cmailMsgLoaded = FALSE;
10159     if (appData.icsActive) {
10160       SendToICS(ics_prefix);
10161       SendToICS("refresh\n");
10162     }
10163 }
10164
10165 void
10166 ExitEvent(status)
10167      int status;
10168 {
10169     exiting++;
10170     if (exiting > 2) {
10171       /* Give up on clean exit */
10172       exit(status);
10173     }
10174     if (exiting > 1) {
10175       /* Keep trying for clean exit */
10176       return;
10177     }
10178
10179     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10180
10181     if (telnetISR != NULL) {
10182       RemoveInputSource(telnetISR);
10183     }
10184     if (icsPR != NoProc) {
10185       DestroyChildProcess(icsPR, TRUE);
10186     }
10187
10188     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10189     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10190
10191     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10192     /* make sure this other one finishes before killing it!                  */
10193     if(endingGame) { int count = 0;
10194         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10195         while(endingGame && count++ < 10) DoSleep(1);
10196         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10197     }
10198
10199     /* Kill off chess programs */
10200     if (first.pr != NoProc) {
10201         ExitAnalyzeMode();
10202         
10203         DoSleep( appData.delayBeforeQuit );
10204         SendToProgram("quit\n", &first);
10205         DoSleep( appData.delayAfterQuit );
10206         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10207     }
10208     if (second.pr != NoProc) {
10209         DoSleep( appData.delayBeforeQuit );
10210         SendToProgram("quit\n", &second);
10211         DoSleep( appData.delayAfterQuit );
10212         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10213     }
10214     if (first.isr != NULL) {
10215         RemoveInputSource(first.isr);
10216     }
10217     if (second.isr != NULL) {
10218         RemoveInputSource(second.isr);
10219     }
10220
10221     ShutDownFrontEnd();
10222     exit(status);
10223 }
10224
10225 void
10226 PauseEvent()
10227 {
10228     if (appData.debugMode)
10229         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10230     if (pausing) {
10231         pausing = FALSE;
10232         ModeHighlight();
10233         if (gameMode == MachinePlaysWhite ||
10234             gameMode == MachinePlaysBlack) {
10235             StartClocks();
10236         } else {
10237             DisplayBothClocks();
10238         }
10239         if (gameMode == PlayFromGameFile) {
10240             if (appData.timeDelay >= 0) 
10241                 AutoPlayGameLoop();
10242         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10243             Reset(FALSE, TRUE);
10244             SendToICS(ics_prefix);
10245             SendToICS("refresh\n");
10246         } else if (currentMove < forwardMostMove) {
10247             ForwardInner(forwardMostMove);
10248         }
10249         pauseExamInvalid = FALSE;
10250     } else {
10251         switch (gameMode) {
10252           default:
10253             return;
10254           case IcsExamining:
10255             pauseExamForwardMostMove = forwardMostMove;
10256             pauseExamInvalid = FALSE;
10257             /* fall through */
10258           case IcsObserving:
10259           case IcsPlayingWhite:
10260           case IcsPlayingBlack:
10261             pausing = TRUE;
10262             ModeHighlight();
10263             return;
10264           case PlayFromGameFile:
10265             (void) StopLoadGameTimer();
10266             pausing = TRUE;
10267             ModeHighlight();
10268             break;
10269           case BeginningOfGame:
10270             if (appData.icsActive) return;
10271             /* else fall through */
10272           case MachinePlaysWhite:
10273           case MachinePlaysBlack:
10274           case TwoMachinesPlay:
10275             if (forwardMostMove == 0)
10276               return;           /* don't pause if no one has moved */
10277             if ((gameMode == MachinePlaysWhite &&
10278                  !WhiteOnMove(forwardMostMove)) ||
10279                 (gameMode == MachinePlaysBlack &&
10280                  WhiteOnMove(forwardMostMove))) {
10281                 StopClocks();
10282             }
10283             pausing = TRUE;
10284             ModeHighlight();
10285             break;
10286         }
10287     }
10288 }
10289
10290 void
10291 EditCommentEvent()
10292 {
10293     char title[MSG_SIZ];
10294
10295     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10296         strcpy(title, _("Edit comment"));
10297     } else {
10298         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10299                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10300                 parseList[currentMove - 1]);
10301     }
10302
10303     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10304 }
10305
10306
10307 void
10308 EditTagsEvent()
10309 {
10310     char *tags = PGNTags(&gameInfo);
10311     EditTagsPopUp(tags);
10312     free(tags);
10313 }
10314
10315 void
10316 AnalyzeModeEvent()
10317 {
10318     if (appData.noChessProgram || gameMode == AnalyzeMode)
10319       return;
10320
10321     if (gameMode != AnalyzeFile) {
10322         if (!appData.icsEngineAnalyze) {
10323                EditGameEvent();
10324                if (gameMode != EditGame) return;
10325         }
10326         ResurrectChessProgram();
10327         SendToProgram("analyze\n", &first);
10328         first.analyzing = TRUE;
10329         /*first.maybeThinking = TRUE;*/
10330         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10331         EngineOutputPopUp();
10332     }
10333     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10334     pausing = FALSE;
10335     ModeHighlight();
10336     SetGameInfo();
10337
10338     StartAnalysisClock();
10339     GetTimeMark(&lastNodeCountTime);
10340     lastNodeCount = 0;
10341 }
10342
10343 void
10344 AnalyzeFileEvent()
10345 {
10346     if (appData.noChessProgram || gameMode == AnalyzeFile)
10347       return;
10348
10349     if (gameMode != AnalyzeMode) {
10350         EditGameEvent();
10351         if (gameMode != EditGame) return;
10352         ResurrectChessProgram();
10353         SendToProgram("analyze\n", &first);
10354         first.analyzing = TRUE;
10355         /*first.maybeThinking = TRUE;*/
10356         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10357         EngineOutputPopUp();
10358     }
10359     gameMode = AnalyzeFile;
10360     pausing = FALSE;
10361     ModeHighlight();
10362     SetGameInfo();
10363
10364     StartAnalysisClock();
10365     GetTimeMark(&lastNodeCountTime);
10366     lastNodeCount = 0;
10367 }
10368
10369 void
10370 MachineWhiteEvent()
10371 {
10372     char buf[MSG_SIZ];
10373     char *bookHit = NULL;
10374
10375     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10376       return;
10377
10378
10379     if (gameMode == PlayFromGameFile || 
10380         gameMode == TwoMachinesPlay  || 
10381         gameMode == Training         || 
10382         gameMode == AnalyzeMode      || 
10383         gameMode == EndOfGame)
10384         EditGameEvent();
10385
10386     if (gameMode == EditPosition) 
10387         EditPositionDone();
10388
10389     if (!WhiteOnMove(currentMove)) {
10390         DisplayError(_("It is not White's turn"), 0);
10391         return;
10392     }
10393   
10394     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10395       ExitAnalyzeMode();
10396
10397     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10398         gameMode == AnalyzeFile)
10399         TruncateGame();
10400
10401     ResurrectChessProgram();    /* in case it isn't running */
10402     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10403         gameMode = MachinePlaysWhite;
10404         ResetClocks();
10405     } else
10406     gameMode = MachinePlaysWhite;
10407     pausing = FALSE;
10408     ModeHighlight();
10409     SetGameInfo();
10410     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10411     DisplayTitle(buf);
10412     if (first.sendName) {
10413       sprintf(buf, "name %s\n", gameInfo.black);
10414       SendToProgram(buf, &first);
10415     }
10416     if (first.sendTime) {
10417       if (first.useColors) {
10418         SendToProgram("black\n", &first); /*gnu kludge*/
10419       }
10420       SendTimeRemaining(&first, TRUE);
10421     }
10422     if (first.useColors) {
10423       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10424     }
10425     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10426     SetMachineThinkingEnables();
10427     first.maybeThinking = TRUE;
10428     StartClocks();
10429     firstMove = FALSE;
10430
10431     if (appData.autoFlipView && !flipView) {
10432       flipView = !flipView;
10433       DrawPosition(FALSE, NULL);
10434       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10435     }
10436
10437     if(bookHit) { // [HGM] book: simulate book reply
10438         static char bookMove[MSG_SIZ]; // a bit generous?
10439
10440         programStats.nodes = programStats.depth = programStats.time = 
10441         programStats.score = programStats.got_only_move = 0;
10442         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10443
10444         strcpy(bookMove, "move ");
10445         strcat(bookMove, bookHit);
10446         HandleMachineMove(bookMove, &first);
10447     }
10448 }
10449
10450 void
10451 MachineBlackEvent()
10452 {
10453     char buf[MSG_SIZ];
10454    char *bookHit = NULL;
10455
10456     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10457         return;
10458
10459
10460     if (gameMode == PlayFromGameFile || 
10461         gameMode == TwoMachinesPlay  || 
10462         gameMode == Training         || 
10463         gameMode == AnalyzeMode      || 
10464         gameMode == EndOfGame)
10465         EditGameEvent();
10466
10467     if (gameMode == EditPosition) 
10468         EditPositionDone();
10469
10470     if (WhiteOnMove(currentMove)) {
10471         DisplayError(_("It is not Black's turn"), 0);
10472         return;
10473     }
10474     
10475     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10476       ExitAnalyzeMode();
10477
10478     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10479         gameMode == AnalyzeFile)
10480         TruncateGame();
10481
10482     ResurrectChessProgram();    /* in case it isn't running */
10483     gameMode = MachinePlaysBlack;
10484     pausing = FALSE;
10485     ModeHighlight();
10486     SetGameInfo();
10487     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10488     DisplayTitle(buf);
10489     if (first.sendName) {
10490       sprintf(buf, "name %s\n", gameInfo.white);
10491       SendToProgram(buf, &first);
10492     }
10493     if (first.sendTime) {
10494       if (first.useColors) {
10495         SendToProgram("white\n", &first); /*gnu kludge*/
10496       }
10497       SendTimeRemaining(&first, FALSE);
10498     }
10499     if (first.useColors) {
10500       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10501     }
10502     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10503     SetMachineThinkingEnables();
10504     first.maybeThinking = TRUE;
10505     StartClocks();
10506
10507     if (appData.autoFlipView && flipView) {
10508       flipView = !flipView;
10509       DrawPosition(FALSE, NULL);
10510       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10511     }
10512     if(bookHit) { // [HGM] book: simulate book reply
10513         static char bookMove[MSG_SIZ]; // a bit generous?
10514
10515         programStats.nodes = programStats.depth = programStats.time = 
10516         programStats.score = programStats.got_only_move = 0;
10517         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10518
10519         strcpy(bookMove, "move ");
10520         strcat(bookMove, bookHit);
10521         HandleMachineMove(bookMove, &first);
10522     }
10523 }
10524
10525
10526 void
10527 DisplayTwoMachinesTitle()
10528 {
10529     char buf[MSG_SIZ];
10530     if (appData.matchGames > 0) {
10531         if (first.twoMachinesColor[0] == 'w') {
10532             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10533                     gameInfo.white, gameInfo.black,
10534                     first.matchWins, second.matchWins,
10535                     matchGame - 1 - (first.matchWins + second.matchWins));
10536         } else {
10537             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10538                     gameInfo.white, gameInfo.black,
10539                     second.matchWins, first.matchWins,
10540                     matchGame - 1 - (first.matchWins + second.matchWins));
10541         }
10542     } else {
10543         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10544     }
10545     DisplayTitle(buf);
10546 }
10547
10548 void
10549 TwoMachinesEvent P((void))
10550 {
10551     int i;
10552     char buf[MSG_SIZ];
10553     ChessProgramState *onmove;
10554     char *bookHit = NULL;
10555     
10556     if (appData.noChessProgram) return;
10557
10558     switch (gameMode) {
10559       case TwoMachinesPlay:
10560         return;
10561       case MachinePlaysWhite:
10562       case MachinePlaysBlack:
10563         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10564             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10565             return;
10566         }
10567         /* fall through */
10568       case BeginningOfGame:
10569       case PlayFromGameFile:
10570       case EndOfGame:
10571         EditGameEvent();
10572         if (gameMode != EditGame) return;
10573         break;
10574       case EditPosition:
10575         EditPositionDone();
10576         break;
10577       case AnalyzeMode:
10578       case AnalyzeFile:
10579         ExitAnalyzeMode();
10580         break;
10581       case EditGame:
10582       default:
10583         break;
10584     }
10585
10586     forwardMostMove = currentMove;
10587     ResurrectChessProgram();    /* in case first program isn't running */
10588
10589     if (second.pr == NULL) {
10590         StartChessProgram(&second);
10591         if (second.protocolVersion == 1) {
10592           TwoMachinesEventIfReady();
10593         } else {
10594           /* kludge: allow timeout for initial "feature" command */
10595           FreezeUI();
10596           DisplayMessage("", _("Starting second chess program"));
10597           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10598         }
10599         return;
10600     }
10601     DisplayMessage("", "");
10602     InitChessProgram(&second, FALSE);
10603     SendToProgram("force\n", &second);
10604     if (startedFromSetupPosition) {
10605         SendBoard(&second, backwardMostMove);
10606     if (appData.debugMode) {
10607         fprintf(debugFP, "Two Machines\n");
10608     }
10609     }
10610     for (i = backwardMostMove; i < forwardMostMove; i++) {
10611         SendMoveToProgram(i, &second);
10612     }
10613
10614     gameMode = TwoMachinesPlay;
10615     pausing = FALSE;
10616     ModeHighlight();
10617     SetGameInfo();
10618     DisplayTwoMachinesTitle();
10619     firstMove = TRUE;
10620     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10621         onmove = &first;
10622     } else {
10623         onmove = &second;
10624     }
10625
10626     SendToProgram(first.computerString, &first);
10627     if (first.sendName) {
10628       sprintf(buf, "name %s\n", second.tidy);
10629       SendToProgram(buf, &first);
10630     }
10631     SendToProgram(second.computerString, &second);
10632     if (second.sendName) {
10633       sprintf(buf, "name %s\n", first.tidy);
10634       SendToProgram(buf, &second);
10635     }
10636
10637     ResetClocks();
10638     if (!first.sendTime || !second.sendTime) {
10639         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10640         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10641     }
10642     if (onmove->sendTime) {
10643       if (onmove->useColors) {
10644         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10645       }
10646       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10647     }
10648     if (onmove->useColors) {
10649       SendToProgram(onmove->twoMachinesColor, onmove);
10650     }
10651     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10652 //    SendToProgram("go\n", onmove);
10653     onmove->maybeThinking = TRUE;
10654     SetMachineThinkingEnables();
10655
10656     StartClocks();
10657
10658     if(bookHit) { // [HGM] book: simulate book reply
10659         static char bookMove[MSG_SIZ]; // a bit generous?
10660
10661         programStats.nodes = programStats.depth = programStats.time = 
10662         programStats.score = programStats.got_only_move = 0;
10663         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10664
10665         strcpy(bookMove, "move ");
10666         strcat(bookMove, bookHit);
10667         HandleMachineMove(bookMove, &first);
10668     }
10669 }
10670
10671 void
10672 TrainingEvent()
10673 {
10674     if (gameMode == Training) {
10675       SetTrainingModeOff();
10676       gameMode = PlayFromGameFile;
10677       DisplayMessage("", _("Training mode off"));
10678     } else {
10679       gameMode = Training;
10680       animateTraining = appData.animate;
10681
10682       /* make sure we are not already at the end of the game */
10683       if (currentMove < forwardMostMove) {
10684         SetTrainingModeOn();
10685         DisplayMessage("", _("Training mode on"));
10686       } else {
10687         gameMode = PlayFromGameFile;
10688         DisplayError(_("Already at end of game"), 0);
10689       }
10690     }
10691     ModeHighlight();
10692 }
10693
10694 void
10695 IcsClientEvent()
10696 {
10697     if (!appData.icsActive) return;
10698     switch (gameMode) {
10699       case IcsPlayingWhite:
10700       case IcsPlayingBlack:
10701       case IcsObserving:
10702       case IcsIdle:
10703       case BeginningOfGame:
10704       case IcsExamining:
10705         return;
10706
10707       case EditGame:
10708         break;
10709
10710       case EditPosition:
10711         EditPositionDone();
10712         break;
10713
10714       case AnalyzeMode:
10715       case AnalyzeFile:
10716         ExitAnalyzeMode();
10717         break;
10718         
10719       default:
10720         EditGameEvent();
10721         break;
10722     }
10723
10724     gameMode = IcsIdle;
10725     ModeHighlight();
10726     return;
10727 }
10728
10729
10730 void
10731 EditGameEvent()
10732 {
10733     int i;
10734
10735     switch (gameMode) {
10736       case Training:
10737         SetTrainingModeOff();
10738         break;
10739       case MachinePlaysWhite:
10740       case MachinePlaysBlack:
10741       case BeginningOfGame:
10742         SendToProgram("force\n", &first);
10743         SetUserThinkingEnables();
10744         break;
10745       case PlayFromGameFile:
10746         (void) StopLoadGameTimer();
10747         if (gameFileFP != NULL) {
10748             gameFileFP = NULL;
10749         }
10750         break;
10751       case EditPosition:
10752         EditPositionDone();
10753         break;
10754       case AnalyzeMode:
10755       case AnalyzeFile:
10756         ExitAnalyzeMode();
10757         SendToProgram("force\n", &first);
10758         break;
10759       case TwoMachinesPlay:
10760         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10761         ResurrectChessProgram();
10762         SetUserThinkingEnables();
10763         break;
10764       case EndOfGame:
10765         ResurrectChessProgram();
10766         break;
10767       case IcsPlayingBlack:
10768       case IcsPlayingWhite:
10769         DisplayError(_("Warning: You are still playing a game"), 0);
10770         break;
10771       case IcsObserving:
10772         DisplayError(_("Warning: You are still observing a game"), 0);
10773         break;
10774       case IcsExamining:
10775         DisplayError(_("Warning: You are still examining a game"), 0);
10776         break;
10777       case IcsIdle:
10778         break;
10779       case EditGame:
10780       default:
10781         return;
10782     }
10783     
10784     pausing = FALSE;
10785     StopClocks();
10786     first.offeredDraw = second.offeredDraw = 0;
10787
10788     if (gameMode == PlayFromGameFile) {
10789         whiteTimeRemaining = timeRemaining[0][currentMove];
10790         blackTimeRemaining = timeRemaining[1][currentMove];
10791         DisplayTitle("");
10792     }
10793
10794     if (gameMode == MachinePlaysWhite ||
10795         gameMode == MachinePlaysBlack ||
10796         gameMode == TwoMachinesPlay ||
10797         gameMode == EndOfGame) {
10798         i = forwardMostMove;
10799         while (i > currentMove) {
10800             SendToProgram("undo\n", &first);
10801             i--;
10802         }
10803         whiteTimeRemaining = timeRemaining[0][currentMove];
10804         blackTimeRemaining = timeRemaining[1][currentMove];
10805         DisplayBothClocks();
10806         if (whiteFlag || blackFlag) {
10807             whiteFlag = blackFlag = 0;
10808         }
10809         DisplayTitle("");
10810     }           
10811     
10812     gameMode = EditGame;
10813     ModeHighlight();
10814     SetGameInfo();
10815 }
10816
10817
10818 void
10819 EditPositionEvent()
10820 {
10821     if (gameMode == EditPosition) {
10822         EditGameEvent();
10823         return;
10824     }
10825     
10826     EditGameEvent();
10827     if (gameMode != EditGame) return;
10828     
10829     gameMode = EditPosition;
10830     ModeHighlight();
10831     SetGameInfo();
10832     if (currentMove > 0)
10833       CopyBoard(boards[0], boards[currentMove]);
10834     
10835     blackPlaysFirst = !WhiteOnMove(currentMove);
10836     ResetClocks();
10837     currentMove = forwardMostMove = backwardMostMove = 0;
10838     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10839     DisplayMove(-1);
10840 }
10841
10842 void
10843 ExitAnalyzeMode()
10844 {
10845     /* [DM] icsEngineAnalyze - possible call from other functions */
10846     if (appData.icsEngineAnalyze) {
10847         appData.icsEngineAnalyze = FALSE;
10848
10849         DisplayMessage("",_("Close ICS engine analyze..."));
10850     }
10851     if (first.analysisSupport && first.analyzing) {
10852       SendToProgram("exit\n", &first);
10853       first.analyzing = FALSE;
10854     }
10855     EngineOutputPopDown();
10856     thinkOutput[0] = NULLCHAR;
10857 }
10858
10859 void
10860 EditPositionDone()
10861 {
10862     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10863
10864     startedFromSetupPosition = TRUE;
10865     InitChessProgram(&first, FALSE);
10866     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10867     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10868         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10869         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10870     } else castlingRights[0][2] = -1;
10871     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10872         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10873         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10874     } else castlingRights[0][5] = -1;
10875     SendToProgram("force\n", &first);
10876     if (blackPlaysFirst) {
10877         strcpy(moveList[0], "");
10878         strcpy(parseList[0], "");
10879         currentMove = forwardMostMove = backwardMostMove = 1;
10880         CopyBoard(boards[1], boards[0]);
10881         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10882         { int i;
10883           epStatus[1] = epStatus[0];
10884           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10885         }
10886     } else {
10887         currentMove = forwardMostMove = backwardMostMove = 0;
10888     }
10889     SendBoard(&first, forwardMostMove);
10890     if (appData.debugMode) {
10891         fprintf(debugFP, "EditPosDone\n");
10892     }
10893     DisplayTitle("");
10894     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10895     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10896     gameMode = EditGame;
10897     ModeHighlight();
10898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10899     ClearHighlights(); /* [AS] */
10900 }
10901
10902 /* Pause for `ms' milliseconds */
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10904 void
10905 TimeDelay(ms)
10906      long ms;
10907 {
10908     TimeMark m1, m2;
10909
10910     GetTimeMark(&m1);
10911     do {
10912         GetTimeMark(&m2);
10913     } while (SubtractTimeMarks(&m2, &m1) < ms);
10914 }
10915
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10917 void
10918 SendMultiLineToICS(buf)
10919      char *buf;
10920 {
10921     char temp[MSG_SIZ+1], *p;
10922     int len;
10923
10924     len = strlen(buf);
10925     if (len > MSG_SIZ)
10926       len = MSG_SIZ;
10927   
10928     strncpy(temp, buf, len);
10929     temp[len] = 0;
10930
10931     p = temp;
10932     while (*p) {
10933         if (*p == '\n' || *p == '\r')
10934           *p = ' ';
10935         ++p;
10936     }
10937
10938     strcat(temp, "\n");
10939     SendToICS(temp);
10940     SendToPlayer(temp, strlen(temp));
10941 }
10942
10943 void
10944 SetWhiteToPlayEvent()
10945 {
10946     if (gameMode == EditPosition) {
10947         blackPlaysFirst = FALSE;
10948         DisplayBothClocks();    /* works because currentMove is 0 */
10949     } else if (gameMode == IcsExamining) {
10950         SendToICS(ics_prefix);
10951         SendToICS("tomove white\n");
10952     }
10953 }
10954
10955 void
10956 SetBlackToPlayEvent()
10957 {
10958     if (gameMode == EditPosition) {
10959         blackPlaysFirst = TRUE;
10960         currentMove = 1;        /* kludge */
10961         DisplayBothClocks();
10962         currentMove = 0;
10963     } else if (gameMode == IcsExamining) {
10964         SendToICS(ics_prefix);
10965         SendToICS("tomove black\n");
10966     }
10967 }
10968
10969 void
10970 EditPositionMenuEvent(selection, x, y)
10971      ChessSquare selection;
10972      int x, y;
10973 {
10974     char buf[MSG_SIZ];
10975     ChessSquare piece = boards[0][y][x];
10976
10977     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10978
10979     switch (selection) {
10980       case ClearBoard:
10981         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10982             SendToICS(ics_prefix);
10983             SendToICS("bsetup clear\n");
10984         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10985             SendToICS(ics_prefix);
10986             SendToICS("clearboard\n");
10987         } else {
10988             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10989                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10990                 for (y = 0; y < BOARD_HEIGHT; y++) {
10991                     if (gameMode == IcsExamining) {
10992                         if (boards[currentMove][y][x] != EmptySquare) {
10993                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10994                                     AAA + x, ONE + y);
10995                             SendToICS(buf);
10996                         }
10997                     } else {
10998                         boards[0][y][x] = p;
10999                     }
11000                 }
11001             }
11002         }
11003         if (gameMode == EditPosition) {
11004             DrawPosition(FALSE, boards[0]);
11005         }
11006         break;
11007
11008       case WhitePlay:
11009         SetWhiteToPlayEvent();
11010         break;
11011
11012       case BlackPlay:
11013         SetBlackToPlayEvent();
11014         break;
11015
11016       case EmptySquare:
11017         if (gameMode == IcsExamining) {
11018             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11019             SendToICS(buf);
11020         } else {
11021             boards[0][y][x] = EmptySquare;
11022             DrawPosition(FALSE, boards[0]);
11023         }
11024         break;
11025
11026       case PromotePiece:
11027         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11028            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11029             selection = (ChessSquare) (PROMOTED piece);
11030         } else if(piece == EmptySquare) selection = WhiteSilver;
11031         else selection = (ChessSquare)((int)piece - 1);
11032         goto defaultlabel;
11033
11034       case DemotePiece:
11035         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11036            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11037             selection = (ChessSquare) (DEMOTED piece);
11038         } else if(piece == EmptySquare) selection = BlackSilver;
11039         else selection = (ChessSquare)((int)piece + 1);       
11040         goto defaultlabel;
11041
11042       case WhiteQueen:
11043       case BlackQueen:
11044         if(gameInfo.variant == VariantShatranj ||
11045            gameInfo.variant == VariantXiangqi  ||
11046            gameInfo.variant == VariantCourier    )
11047             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11048         goto defaultlabel;
11049
11050       case WhiteKing:
11051       case BlackKing:
11052         if(gameInfo.variant == VariantXiangqi)
11053             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11054         if(gameInfo.variant == VariantKnightmate)
11055             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11056       default:
11057         defaultlabel:
11058         if (gameMode == IcsExamining) {
11059             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11060                     PieceToChar(selection), AAA + x, ONE + y);
11061             SendToICS(buf);
11062         } else {
11063             boards[0][y][x] = selection;
11064             DrawPosition(FALSE, boards[0]);
11065         }
11066         break;
11067     }
11068 }
11069
11070
11071 void
11072 DropMenuEvent(selection, x, y)
11073      ChessSquare selection;
11074      int x, y;
11075 {
11076     ChessMove moveType;
11077
11078     switch (gameMode) {
11079       case IcsPlayingWhite:
11080       case MachinePlaysBlack:
11081         if (!WhiteOnMove(currentMove)) {
11082             DisplayMoveError(_("It is Black's turn"));
11083             return;
11084         }
11085         moveType = WhiteDrop;
11086         break;
11087       case IcsPlayingBlack:
11088       case MachinePlaysWhite:
11089         if (WhiteOnMove(currentMove)) {
11090             DisplayMoveError(_("It is White's turn"));
11091             return;
11092         }
11093         moveType = BlackDrop;
11094         break;
11095       case EditGame:
11096         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11097         break;
11098       default:
11099         return;
11100     }
11101
11102     if (moveType == BlackDrop && selection < BlackPawn) {
11103       selection = (ChessSquare) ((int) selection
11104                                  + (int) BlackPawn - (int) WhitePawn);
11105     }
11106     if (boards[currentMove][y][x] != EmptySquare) {
11107         DisplayMoveError(_("That square is occupied"));
11108         return;
11109     }
11110
11111     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11112 }
11113
11114 void
11115 AcceptEvent()
11116 {
11117     /* Accept a pending offer of any kind from opponent */
11118     
11119     if (appData.icsActive) {
11120         SendToICS(ics_prefix);
11121         SendToICS("accept\n");
11122     } else if (cmailMsgLoaded) {
11123         if (currentMove == cmailOldMove &&
11124             commentList[cmailOldMove] != NULL &&
11125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11126                    "Black offers a draw" : "White offers a draw")) {
11127             TruncateGame();
11128             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11130         } else {
11131             DisplayError(_("There is no pending offer on this move"), 0);
11132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11133         }
11134     } else {
11135         /* Not used for offers from chess program */
11136     }
11137 }
11138
11139 void
11140 DeclineEvent()
11141 {
11142     /* Decline a pending offer of any kind from opponent */
11143     
11144     if (appData.icsActive) {
11145         SendToICS(ics_prefix);
11146         SendToICS("decline\n");
11147     } else if (cmailMsgLoaded) {
11148         if (currentMove == cmailOldMove &&
11149             commentList[cmailOldMove] != NULL &&
11150             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11151                    "Black offers a draw" : "White offers a draw")) {
11152 #ifdef NOTDEF
11153             AppendComment(cmailOldMove, "Draw declined");
11154             DisplayComment(cmailOldMove - 1, "Draw declined");
11155 #endif /*NOTDEF*/
11156         } else {
11157             DisplayError(_("There is no pending offer on this move"), 0);
11158         }
11159     } else {
11160         /* Not used for offers from chess program */
11161     }
11162 }
11163
11164 void
11165 RematchEvent()
11166 {
11167     /* Issue ICS rematch command */
11168     if (appData.icsActive) {
11169         SendToICS(ics_prefix);
11170         SendToICS("rematch\n");
11171     }
11172 }
11173
11174 void
11175 CallFlagEvent()
11176 {
11177     /* Call your opponent's flag (claim a win on time) */
11178     if (appData.icsActive) {
11179         SendToICS(ics_prefix);
11180         SendToICS("flag\n");
11181     } else {
11182         switch (gameMode) {
11183           default:
11184             return;
11185           case MachinePlaysWhite:
11186             if (whiteFlag) {
11187                 if (blackFlag)
11188                   GameEnds(GameIsDrawn, "Both players ran out of time",
11189                            GE_PLAYER);
11190                 else
11191                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11192             } else {
11193                 DisplayError(_("Your opponent is not out of time"), 0);
11194             }
11195             break;
11196           case MachinePlaysBlack:
11197             if (blackFlag) {
11198                 if (whiteFlag)
11199                   GameEnds(GameIsDrawn, "Both players ran out of time",
11200                            GE_PLAYER);
11201                 else
11202                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11203             } else {
11204                 DisplayError(_("Your opponent is not out of time"), 0);
11205             }
11206             break;
11207         }
11208     }
11209 }
11210
11211 void
11212 DrawEvent()
11213 {
11214     /* Offer draw or accept pending draw offer from opponent */
11215     
11216     if (appData.icsActive) {
11217         /* Note: tournament rules require draw offers to be
11218            made after you make your move but before you punch
11219            your clock.  Currently ICS doesn't let you do that;
11220            instead, you immediately punch your clock after making
11221            a move, but you can offer a draw at any time. */
11222         
11223         SendToICS(ics_prefix);
11224         SendToICS("draw\n");
11225     } else if (cmailMsgLoaded) {
11226         if (currentMove == cmailOldMove &&
11227             commentList[cmailOldMove] != NULL &&
11228             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11229                    "Black offers a draw" : "White offers a draw")) {
11230             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11232         } else if (currentMove == cmailOldMove + 1) {
11233             char *offer = WhiteOnMove(cmailOldMove) ?
11234               "White offers a draw" : "Black offers a draw";
11235             AppendComment(currentMove, offer);
11236             DisplayComment(currentMove - 1, offer);
11237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11238         } else {
11239             DisplayError(_("You must make your move before offering a draw"), 0);
11240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11241         }
11242     } else if (first.offeredDraw) {
11243         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11244     } else {
11245         if (first.sendDrawOffers) {
11246             SendToProgram("draw\n", &first);
11247             userOfferedDraw = TRUE;
11248         }
11249     }
11250 }
11251
11252 void
11253 AdjournEvent()
11254 {
11255     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11256     
11257     if (appData.icsActive) {
11258         SendToICS(ics_prefix);
11259         SendToICS("adjourn\n");
11260     } else {
11261         /* Currently GNU Chess doesn't offer or accept Adjourns */
11262     }
11263 }
11264
11265
11266 void
11267 AbortEvent()
11268 {
11269     /* Offer Abort or accept pending Abort offer from opponent */
11270     
11271     if (appData.icsActive) {
11272         SendToICS(ics_prefix);
11273         SendToICS("abort\n");
11274     } else {
11275         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11276     }
11277 }
11278
11279 void
11280 ResignEvent()
11281 {
11282     /* Resign.  You can do this even if it's not your turn. */
11283     
11284     if (appData.icsActive) {
11285         SendToICS(ics_prefix);
11286         SendToICS("resign\n");
11287     } else {
11288         switch (gameMode) {
11289           case MachinePlaysWhite:
11290             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11291             break;
11292           case MachinePlaysBlack:
11293             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11294             break;
11295           case EditGame:
11296             if (cmailMsgLoaded) {
11297                 TruncateGame();
11298                 if (WhiteOnMove(cmailOldMove)) {
11299                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11300                 } else {
11301                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11302                 }
11303                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11304             }
11305             break;
11306           default:
11307             break;
11308         }
11309     }
11310 }
11311
11312
11313 void
11314 StopObservingEvent()
11315 {
11316     /* Stop observing current games */
11317     SendToICS(ics_prefix);
11318     SendToICS("unobserve\n");
11319 }
11320
11321 void
11322 StopExaminingEvent()
11323 {
11324     /* Stop observing current game */
11325     SendToICS(ics_prefix);
11326     SendToICS("unexamine\n");
11327 }
11328
11329 void
11330 ForwardInner(target)
11331      int target;
11332 {
11333     int limit;
11334
11335     if (appData.debugMode)
11336         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11337                 target, currentMove, forwardMostMove);
11338
11339     if (gameMode == EditPosition)
11340       return;
11341
11342     if (gameMode == PlayFromGameFile && !pausing)
11343       PauseEvent();
11344     
11345     if (gameMode == IcsExamining && pausing)
11346       limit = pauseExamForwardMostMove;
11347     else
11348       limit = forwardMostMove;
11349     
11350     if (target > limit) target = limit;
11351
11352     if (target > 0 && moveList[target - 1][0]) {
11353         int fromX, fromY, toX, toY;
11354         toX = moveList[target - 1][2] - AAA;
11355         toY = moveList[target - 1][3] - ONE;
11356         if (moveList[target - 1][1] == '@') {
11357             if (appData.highlightLastMove) {
11358                 SetHighlights(-1, -1, toX, toY);
11359             }
11360         } else {
11361             fromX = moveList[target - 1][0] - AAA;
11362             fromY = moveList[target - 1][1] - ONE;
11363             if (target == currentMove + 1) {
11364                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11365             }
11366             if (appData.highlightLastMove) {
11367                 SetHighlights(fromX, fromY, toX, toY);
11368             }
11369         }
11370     }
11371     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11372         gameMode == Training || gameMode == PlayFromGameFile || 
11373         gameMode == AnalyzeFile) {
11374         while (currentMove < target) {
11375             SendMoveToProgram(currentMove++, &first);
11376         }
11377     } else {
11378         currentMove = target;
11379     }
11380     
11381     if (gameMode == EditGame || gameMode == EndOfGame) {
11382         whiteTimeRemaining = timeRemaining[0][currentMove];
11383         blackTimeRemaining = timeRemaining[1][currentMove];
11384     }
11385     DisplayBothClocks();
11386     DisplayMove(currentMove - 1);
11387     DrawPosition(FALSE, boards[currentMove]);
11388     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11389     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11390         DisplayComment(currentMove - 1, commentList[currentMove]);
11391     }
11392 }
11393
11394
11395 void
11396 ForwardEvent()
11397 {
11398     if (gameMode == IcsExamining && !pausing) {
11399         SendToICS(ics_prefix);
11400         SendToICS("forward\n");
11401     } else {
11402         ForwardInner(currentMove + 1);
11403     }
11404 }
11405
11406 void
11407 ToEndEvent()
11408 {
11409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11410         /* to optimze, we temporarily turn off analysis mode while we feed
11411          * the remaining moves to the engine. Otherwise we get analysis output
11412          * after each move.
11413          */ 
11414         if (first.analysisSupport) {
11415           SendToProgram("exit\nforce\n", &first);
11416           first.analyzing = FALSE;
11417         }
11418     }
11419         
11420     if (gameMode == IcsExamining && !pausing) {
11421         SendToICS(ics_prefix);
11422         SendToICS("forward 999999\n");
11423     } else {
11424         ForwardInner(forwardMostMove);
11425     }
11426
11427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11428         /* we have fed all the moves, so reactivate analysis mode */
11429         SendToProgram("analyze\n", &first);
11430         first.analyzing = TRUE;
11431         /*first.maybeThinking = TRUE;*/
11432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11433     }
11434 }
11435
11436 void
11437 BackwardInner(target)
11438      int target;
11439 {
11440     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11441
11442     if (appData.debugMode)
11443         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11444                 target, currentMove, forwardMostMove);
11445
11446     if (gameMode == EditPosition) return;
11447     if (currentMove <= backwardMostMove) {
11448         ClearHighlights();
11449         DrawPosition(full_redraw, boards[currentMove]);
11450         return;
11451     }
11452     if (gameMode == PlayFromGameFile && !pausing)
11453       PauseEvent();
11454     
11455     if (moveList[target][0]) {
11456         int fromX, fromY, toX, toY;
11457         toX = moveList[target][2] - AAA;
11458         toY = moveList[target][3] - ONE;
11459         if (moveList[target][1] == '@') {
11460             if (appData.highlightLastMove) {
11461                 SetHighlights(-1, -1, toX, toY);
11462             }
11463         } else {
11464             fromX = moveList[target][0] - AAA;
11465             fromY = moveList[target][1] - ONE;
11466             if (target == currentMove - 1) {
11467                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11468             }
11469             if (appData.highlightLastMove) {
11470                 SetHighlights(fromX, fromY, toX, toY);
11471             }
11472         }
11473     }
11474     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11475         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11476         while (currentMove > target) {
11477             SendToProgram("undo\n", &first);
11478             currentMove--;
11479         }
11480     } else {
11481         currentMove = target;
11482     }
11483     
11484     if (gameMode == EditGame || gameMode == EndOfGame) {
11485         whiteTimeRemaining = timeRemaining[0][currentMove];
11486         blackTimeRemaining = timeRemaining[1][currentMove];
11487     }
11488     DisplayBothClocks();
11489     DisplayMove(currentMove - 1);
11490     DrawPosition(full_redraw, boards[currentMove]);
11491     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11492     // [HGM] PV info: routine tests if comment empty
11493     DisplayComment(currentMove - 1, commentList[currentMove]);
11494 }
11495
11496 void
11497 BackwardEvent()
11498 {
11499     if (gameMode == IcsExamining && !pausing) {
11500         SendToICS(ics_prefix);
11501         SendToICS("backward\n");
11502     } else {
11503         BackwardInner(currentMove - 1);
11504     }
11505 }
11506
11507 void
11508 ToStartEvent()
11509 {
11510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11511         /* to optimze, we temporarily turn off analysis mode while we undo
11512          * all the moves. Otherwise we get analysis output after each undo.
11513          */ 
11514         if (first.analysisSupport) {
11515           SendToProgram("exit\nforce\n", &first);
11516           first.analyzing = FALSE;
11517         }
11518     }
11519
11520     if (gameMode == IcsExamining && !pausing) {
11521         SendToICS(ics_prefix);
11522         SendToICS("backward 999999\n");
11523     } else {
11524         BackwardInner(backwardMostMove);
11525     }
11526
11527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11528         /* we have fed all the moves, so reactivate analysis mode */
11529         SendToProgram("analyze\n", &first);
11530         first.analyzing = TRUE;
11531         /*first.maybeThinking = TRUE;*/
11532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11533     }
11534 }
11535
11536 void
11537 ToNrEvent(int to)
11538 {
11539   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11540   if (to >= forwardMostMove) to = forwardMostMove;
11541   if (to <= backwardMostMove) to = backwardMostMove;
11542   if (to < currentMove) {
11543     BackwardInner(to);
11544   } else {
11545     ForwardInner(to);
11546   }
11547 }
11548
11549 void
11550 RevertEvent()
11551 {
11552     if (gameMode != IcsExamining) {
11553         DisplayError(_("You are not examining a game"), 0);
11554         return;
11555     }
11556     if (pausing) {
11557         DisplayError(_("You can't revert while pausing"), 0);
11558         return;
11559     }
11560     SendToICS(ics_prefix);
11561     SendToICS("revert\n");
11562 }
11563
11564 void
11565 RetractMoveEvent()
11566 {
11567     switch (gameMode) {
11568       case MachinePlaysWhite:
11569       case MachinePlaysBlack:
11570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11571             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11572             return;
11573         }
11574         if (forwardMostMove < 2) return;
11575         currentMove = forwardMostMove = forwardMostMove - 2;
11576         whiteTimeRemaining = timeRemaining[0][currentMove];
11577         blackTimeRemaining = timeRemaining[1][currentMove];
11578         DisplayBothClocks();
11579         DisplayMove(currentMove - 1);
11580         ClearHighlights();/*!! could figure this out*/
11581         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11582         SendToProgram("remove\n", &first);
11583         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11584         break;
11585
11586       case BeginningOfGame:
11587       default:
11588         break;
11589
11590       case IcsPlayingWhite:
11591       case IcsPlayingBlack:
11592         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11593             SendToICS(ics_prefix);
11594             SendToICS("takeback 2\n");
11595         } else {
11596             SendToICS(ics_prefix);
11597             SendToICS("takeback 1\n");
11598         }
11599         break;
11600     }
11601 }
11602
11603 void
11604 MoveNowEvent()
11605 {
11606     ChessProgramState *cps;
11607
11608     switch (gameMode) {
11609       case MachinePlaysWhite:
11610         if (!WhiteOnMove(forwardMostMove)) {
11611             DisplayError(_("It is your turn"), 0);
11612             return;
11613         }
11614         cps = &first;
11615         break;
11616       case MachinePlaysBlack:
11617         if (WhiteOnMove(forwardMostMove)) {
11618             DisplayError(_("It is your turn"), 0);
11619             return;
11620         }
11621         cps = &first;
11622         break;
11623       case TwoMachinesPlay:
11624         if (WhiteOnMove(forwardMostMove) ==
11625             (first.twoMachinesColor[0] == 'w')) {
11626             cps = &first;
11627         } else {
11628             cps = &second;
11629         }
11630         break;
11631       case BeginningOfGame:
11632       default:
11633         return;
11634     }
11635     SendToProgram("?\n", cps);
11636 }
11637
11638 void
11639 TruncateGameEvent()
11640 {
11641     EditGameEvent();
11642     if (gameMode != EditGame) return;
11643     TruncateGame();
11644 }
11645
11646 void
11647 TruncateGame()
11648 {
11649     if (forwardMostMove > currentMove) {
11650         if (gameInfo.resultDetails != NULL) {
11651             free(gameInfo.resultDetails);
11652             gameInfo.resultDetails = NULL;
11653             gameInfo.result = GameUnfinished;
11654         }
11655         forwardMostMove = currentMove;
11656         HistorySet(parseList, backwardMostMove, forwardMostMove,
11657                    currentMove-1);
11658     }
11659 }
11660
11661 void
11662 HintEvent()
11663 {
11664     if (appData.noChessProgram) return;
11665     switch (gameMode) {
11666       case MachinePlaysWhite:
11667         if (WhiteOnMove(forwardMostMove)) {
11668             DisplayError(_("Wait until your turn"), 0);
11669             return;
11670         }
11671         break;
11672       case BeginningOfGame:
11673       case MachinePlaysBlack:
11674         if (!WhiteOnMove(forwardMostMove)) {
11675             DisplayError(_("Wait until your turn"), 0);
11676             return;
11677         }
11678         break;
11679       default:
11680         DisplayError(_("No hint available"), 0);
11681         return;
11682     }
11683     SendToProgram("hint\n", &first);
11684     hintRequested = TRUE;
11685 }
11686
11687 void
11688 BookEvent()
11689 {
11690     if (appData.noChessProgram) return;
11691     switch (gameMode) {
11692       case MachinePlaysWhite:
11693         if (WhiteOnMove(forwardMostMove)) {
11694             DisplayError(_("Wait until your turn"), 0);
11695             return;
11696         }
11697         break;
11698       case BeginningOfGame:
11699       case MachinePlaysBlack:
11700         if (!WhiteOnMove(forwardMostMove)) {
11701             DisplayError(_("Wait until your turn"), 0);
11702             return;
11703         }
11704         break;
11705       case EditPosition:
11706         EditPositionDone();
11707         break;
11708       case TwoMachinesPlay:
11709         return;
11710       default:
11711         break;
11712     }
11713     SendToProgram("bk\n", &first);
11714     bookOutput[0] = NULLCHAR;
11715     bookRequested = TRUE;
11716 }
11717
11718 void
11719 AboutGameEvent()
11720 {
11721     char *tags = PGNTags(&gameInfo);
11722     TagsPopUp(tags, CmailMsg());
11723     free(tags);
11724 }
11725
11726 /* end button procedures */
11727
11728 void
11729 PrintPosition(fp, move)
11730      FILE *fp;
11731      int move;
11732 {
11733     int i, j;
11734     
11735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11736         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11737             char c = PieceToChar(boards[move][i][j]);
11738             fputc(c == 'x' ? '.' : c, fp);
11739             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11740         }
11741     }
11742     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11743       fprintf(fp, "white to play\n");
11744     else
11745       fprintf(fp, "black to play\n");
11746 }
11747
11748 void
11749 PrintOpponents(fp)
11750      FILE *fp;
11751 {
11752     if (gameInfo.white != NULL) {
11753         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11754     } else {
11755         fprintf(fp, "\n");
11756     }
11757 }
11758
11759 /* Find last component of program's own name, using some heuristics */
11760 void
11761 TidyProgramName(prog, host, buf)
11762      char *prog, *host, buf[MSG_SIZ];
11763 {
11764     char *p, *q;
11765     int local = (strcmp(host, "localhost") == 0);
11766     while (!local && (p = strchr(prog, ';')) != NULL) {
11767         p++;
11768         while (*p == ' ') p++;
11769         prog = p;
11770     }
11771     if (*prog == '"' || *prog == '\'') {
11772         q = strchr(prog + 1, *prog);
11773     } else {
11774         q = strchr(prog, ' ');
11775     }
11776     if (q == NULL) q = prog + strlen(prog);
11777     p = q;
11778     while (p >= prog && *p != '/' && *p != '\\') p--;
11779     p++;
11780     if(p == prog && *p == '"') p++;
11781     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11782     memcpy(buf, p, q - p);
11783     buf[q - p] = NULLCHAR;
11784     if (!local) {
11785         strcat(buf, "@");
11786         strcat(buf, host);
11787     }
11788 }
11789
11790 char *
11791 TimeControlTagValue()
11792 {
11793     char buf[MSG_SIZ];
11794     if (!appData.clockMode) {
11795         strcpy(buf, "-");
11796     } else if (movesPerSession > 0) {
11797         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11798     } else if (timeIncrement == 0) {
11799         sprintf(buf, "%ld", timeControl/1000);
11800     } else {
11801         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11802     }
11803     return StrSave(buf);
11804 }
11805
11806 void
11807 SetGameInfo()
11808 {
11809     /* This routine is used only for certain modes */
11810     VariantClass v = gameInfo.variant;
11811     ClearGameInfo(&gameInfo);
11812     gameInfo.variant = v;
11813
11814     switch (gameMode) {
11815       case MachinePlaysWhite:
11816         gameInfo.event = StrSave( appData.pgnEventHeader );
11817         gameInfo.site = StrSave(HostName());
11818         gameInfo.date = PGNDate();
11819         gameInfo.round = StrSave("-");
11820         gameInfo.white = StrSave(first.tidy);
11821         gameInfo.black = StrSave(UserName());
11822         gameInfo.timeControl = TimeControlTagValue();
11823         break;
11824
11825       case MachinePlaysBlack:
11826         gameInfo.event = StrSave( appData.pgnEventHeader );
11827         gameInfo.site = StrSave(HostName());
11828         gameInfo.date = PGNDate();
11829         gameInfo.round = StrSave("-");
11830         gameInfo.white = StrSave(UserName());
11831         gameInfo.black = StrSave(first.tidy);
11832         gameInfo.timeControl = TimeControlTagValue();
11833         break;
11834
11835       case TwoMachinesPlay:
11836         gameInfo.event = StrSave( appData.pgnEventHeader );
11837         gameInfo.site = StrSave(HostName());
11838         gameInfo.date = PGNDate();
11839         if (matchGame > 0) {
11840             char buf[MSG_SIZ];
11841             sprintf(buf, "%d", matchGame);
11842             gameInfo.round = StrSave(buf);
11843         } else {
11844             gameInfo.round = StrSave("-");
11845         }
11846         if (first.twoMachinesColor[0] == 'w') {
11847             gameInfo.white = StrSave(first.tidy);
11848             gameInfo.black = StrSave(second.tidy);
11849         } else {
11850             gameInfo.white = StrSave(second.tidy);
11851             gameInfo.black = StrSave(first.tidy);
11852         }
11853         gameInfo.timeControl = TimeControlTagValue();
11854         break;
11855
11856       case EditGame:
11857         gameInfo.event = StrSave("Edited game");
11858         gameInfo.site = StrSave(HostName());
11859         gameInfo.date = PGNDate();
11860         gameInfo.round = StrSave("-");
11861         gameInfo.white = StrSave("-");
11862         gameInfo.black = StrSave("-");
11863         break;
11864
11865       case EditPosition:
11866         gameInfo.event = StrSave("Edited position");
11867         gameInfo.site = StrSave(HostName());
11868         gameInfo.date = PGNDate();
11869         gameInfo.round = StrSave("-");
11870         gameInfo.white = StrSave("-");
11871         gameInfo.black = StrSave("-");
11872         break;
11873
11874       case IcsPlayingWhite:
11875       case IcsPlayingBlack:
11876       case IcsObserving:
11877       case IcsExamining:
11878         break;
11879
11880       case PlayFromGameFile:
11881         gameInfo.event = StrSave("Game from non-PGN file");
11882         gameInfo.site = StrSave(HostName());
11883         gameInfo.date = PGNDate();
11884         gameInfo.round = StrSave("-");
11885         gameInfo.white = StrSave("?");
11886         gameInfo.black = StrSave("?");
11887         break;
11888
11889       default:
11890         break;
11891     }
11892 }
11893
11894 void
11895 ReplaceComment(index, text)
11896      int index;
11897      char *text;
11898 {
11899     int len;
11900
11901     while (*text == '\n') text++;
11902     len = strlen(text);
11903     while (len > 0 && text[len - 1] == '\n') len--;
11904
11905     if (commentList[index] != NULL)
11906       free(commentList[index]);
11907
11908     if (len == 0) {
11909         commentList[index] = NULL;
11910         return;
11911     }
11912     commentList[index] = (char *) malloc(len + 2);
11913     strncpy(commentList[index], text, len);
11914     commentList[index][len] = '\n';
11915     commentList[index][len + 1] = NULLCHAR;
11916 }
11917
11918 void
11919 CrushCRs(text)
11920      char *text;
11921 {
11922   char *p = text;
11923   char *q = text;
11924   char ch;
11925
11926   do {
11927     ch = *p++;
11928     if (ch == '\r') continue;
11929     *q++ = ch;
11930   } while (ch != '\0');
11931 }
11932
11933 void
11934 AppendComment(index, text)
11935      int index;
11936      char *text;
11937 {
11938     int oldlen, len;
11939     char *old;
11940
11941     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11942
11943     CrushCRs(text);
11944     while (*text == '\n') text++;
11945     len = strlen(text);
11946     while (len > 0 && text[len - 1] == '\n') len--;
11947
11948     if (len == 0) return;
11949
11950     if (commentList[index] != NULL) {
11951         old = commentList[index];
11952         oldlen = strlen(old);
11953         commentList[index] = (char *) malloc(oldlen + len + 2);
11954         strcpy(commentList[index], old);
11955         free(old);
11956         strncpy(&commentList[index][oldlen], text, len);
11957         commentList[index][oldlen + len] = '\n';
11958         commentList[index][oldlen + len + 1] = NULLCHAR;
11959     } else {
11960         commentList[index] = (char *) malloc(len + 2);
11961         strncpy(commentList[index], text, len);
11962         commentList[index][len] = '\n';
11963         commentList[index][len + 1] = NULLCHAR;
11964     }
11965 }
11966
11967 static char * FindStr( char * text, char * sub_text )
11968 {
11969     char * result = strstr( text, sub_text );
11970
11971     if( result != NULL ) {
11972         result += strlen( sub_text );
11973     }
11974
11975     return result;
11976 }
11977
11978 /* [AS] Try to extract PV info from PGN comment */
11979 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11980 char *GetInfoFromComment( int index, char * text )
11981 {
11982     char * sep = text;
11983
11984     if( text != NULL && index > 0 ) {
11985         int score = 0;
11986         int depth = 0;
11987         int time = -1, sec = 0, deci;
11988         char * s_eval = FindStr( text, "[%eval " );
11989         char * s_emt = FindStr( text, "[%emt " );
11990
11991         if( s_eval != NULL || s_emt != NULL ) {
11992             /* New style */
11993             char delim;
11994
11995             if( s_eval != NULL ) {
11996                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11997                     return text;
11998                 }
11999
12000                 if( delim != ']' ) {
12001                     return text;
12002                 }
12003             }
12004
12005             if( s_emt != NULL ) {
12006             }
12007         }
12008         else {
12009             /* We expect something like: [+|-]nnn.nn/dd */
12010             int score_lo = 0;
12011
12012             sep = strchr( text, '/' );
12013             if( sep == NULL || sep < (text+4) ) {
12014                 return text;
12015             }
12016
12017             time = -1; sec = -1; deci = -1;
12018             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12019                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12020                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12021                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12022                 return text;
12023             }
12024
12025             if( score_lo < 0 || score_lo >= 100 ) {
12026                 return text;
12027             }
12028
12029             if(sec >= 0) time = 600*time + 10*sec; else
12030             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12031
12032             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12033
12034             /* [HGM] PV time: now locate end of PV info */
12035             while( *++sep >= '0' && *sep <= '9'); // strip depth
12036             if(time >= 0)
12037             while( *++sep >= '0' && *sep <= '9'); // strip time
12038             if(sec >= 0)
12039             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12040             if(deci >= 0)
12041             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12042             while(*sep == ' ') sep++;
12043         }
12044
12045         if( depth <= 0 ) {
12046             return text;
12047         }
12048
12049         if( time < 0 ) {
12050             time = -1;
12051         }
12052
12053         pvInfoList[index-1].depth = depth;
12054         pvInfoList[index-1].score = score;
12055         pvInfoList[index-1].time  = 10*time; // centi-sec
12056     }
12057     return sep;
12058 }
12059
12060 void
12061 SendToProgram(message, cps)
12062      char *message;
12063      ChessProgramState *cps;
12064 {
12065     int count, outCount, error;
12066     char buf[MSG_SIZ];
12067
12068     if (cps->pr == NULL) return;
12069     Attention(cps);
12070     
12071     if (appData.debugMode) {
12072         TimeMark now;
12073         GetTimeMark(&now);
12074         fprintf(debugFP, "%ld >%-6s: %s", 
12075                 SubtractTimeMarks(&now, &programStartTime),
12076                 cps->which, message);
12077     }
12078     
12079     count = strlen(message);
12080     outCount = OutputToProcess(cps->pr, message, count, &error);
12081     if (outCount < count && !exiting 
12082                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12083         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12084         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12085             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12086                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12087                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12088             } else {
12089                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12090             }
12091             gameInfo.resultDetails = buf;
12092         }
12093         DisplayFatalError(buf, error, 1);
12094     }
12095 }
12096
12097 void
12098 ReceiveFromProgram(isr, closure, message, count, error)
12099      InputSourceRef isr;
12100      VOIDSTAR closure;
12101      char *message;
12102      int count;
12103      int error;
12104 {
12105     char *end_str;
12106     char buf[MSG_SIZ];
12107     ChessProgramState *cps = (ChessProgramState *)closure;
12108
12109     if (isr != cps->isr) return; /* Killed intentionally */
12110     if (count <= 0) {
12111         if (count == 0) {
12112             sprintf(buf,
12113                     _("Error: %s chess program (%s) exited unexpectedly"),
12114                     cps->which, cps->program);
12115         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12116                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12117                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12118                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12119                 } else {
12120                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12121                 }
12122                 gameInfo.resultDetails = buf;
12123             }
12124             RemoveInputSource(cps->isr);
12125             DisplayFatalError(buf, 0, 1);
12126         } else {
12127             sprintf(buf,
12128                     _("Error reading from %s chess program (%s)"),
12129                     cps->which, cps->program);
12130             RemoveInputSource(cps->isr);
12131
12132             /* [AS] Program is misbehaving badly... kill it */
12133             if( count == -2 ) {
12134                 DestroyChildProcess( cps->pr, 9 );
12135                 cps->pr = NoProc;
12136             }
12137
12138             DisplayFatalError(buf, error, 1);
12139         }
12140         return;
12141     }
12142     
12143     if ((end_str = strchr(message, '\r')) != NULL)
12144       *end_str = NULLCHAR;
12145     if ((end_str = strchr(message, '\n')) != NULL)
12146       *end_str = NULLCHAR;
12147     
12148     if (appData.debugMode) {
12149         TimeMark now; int print = 1;
12150         char *quote = ""; char c; int i;
12151
12152         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12153                 char start = message[0];
12154                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12155                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12156                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12157                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12158                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12159                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12160                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12161                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12162                         { quote = "# "; print = (appData.engineComments == 2); }
12163                 message[0] = start; // restore original message
12164         }
12165         if(print) {
12166                 GetTimeMark(&now);
12167                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12168                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12169                         quote,
12170                         message);
12171         }
12172     }
12173
12174     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12175     if (appData.icsEngineAnalyze) {
12176         if (strstr(message, "whisper") != NULL ||
12177              strstr(message, "kibitz") != NULL || 
12178             strstr(message, "tellics") != NULL) return;
12179     }
12180
12181     HandleMachineMove(message, cps);
12182 }
12183
12184
12185 void
12186 SendTimeControl(cps, mps, tc, inc, sd, st)
12187      ChessProgramState *cps;
12188      int mps, inc, sd, st;
12189      long tc;
12190 {
12191     char buf[MSG_SIZ];
12192     int seconds;
12193
12194     if( timeControl_2 > 0 ) {
12195         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12196             tc = timeControl_2;
12197         }
12198     }
12199     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12200     inc /= cps->timeOdds;
12201     st  /= cps->timeOdds;
12202
12203     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12204
12205     if (st > 0) {
12206       /* Set exact time per move, normally using st command */
12207       if (cps->stKludge) {
12208         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12209         seconds = st % 60;
12210         if (seconds == 0) {
12211           sprintf(buf, "level 1 %d\n", st/60);
12212         } else {
12213           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12214         }
12215       } else {
12216         sprintf(buf, "st %d\n", st);
12217       }
12218     } else {
12219       /* Set conventional or incremental time control, using level command */
12220       if (seconds == 0) {
12221         /* Note old gnuchess bug -- minutes:seconds used to not work.
12222            Fixed in later versions, but still avoid :seconds
12223            when seconds is 0. */
12224         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12225       } else {
12226         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12227                 seconds, inc/1000);
12228       }
12229     }
12230     SendToProgram(buf, cps);
12231
12232     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12233     /* Orthogonally, limit search to given depth */
12234     if (sd > 0) {
12235       if (cps->sdKludge) {
12236         sprintf(buf, "depth\n%d\n", sd);
12237       } else {
12238         sprintf(buf, "sd %d\n", sd);
12239       }
12240       SendToProgram(buf, cps);
12241     }
12242
12243     if(cps->nps > 0) { /* [HGM] nps */
12244         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12245         else {
12246                 sprintf(buf, "nps %d\n", cps->nps);
12247               SendToProgram(buf, cps);
12248         }
12249     }
12250 }
12251
12252 ChessProgramState *WhitePlayer()
12253 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12254 {
12255     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12256        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12257         return &second;
12258     return &first;
12259 }
12260
12261 void
12262 SendTimeRemaining(cps, machineWhite)
12263      ChessProgramState *cps;
12264      int /*boolean*/ machineWhite;
12265 {
12266     char message[MSG_SIZ];
12267     long time, otime;
12268
12269     /* Note: this routine must be called when the clocks are stopped
12270        or when they have *just* been set or switched; otherwise
12271        it will be off by the time since the current tick started.
12272     */
12273     if (machineWhite) {
12274         time = whiteTimeRemaining / 10;
12275         otime = blackTimeRemaining / 10;
12276     } else {
12277         time = blackTimeRemaining / 10;
12278         otime = whiteTimeRemaining / 10;
12279     }
12280     /* [HGM] translate opponent's time by time-odds factor */
12281     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12282     if (appData.debugMode) {
12283         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12284     }
12285
12286     if (time <= 0) time = 1;
12287     if (otime <= 0) otime = 1;
12288     
12289     sprintf(message, "time %ld\n", time);
12290     SendToProgram(message, cps);
12291
12292     sprintf(message, "otim %ld\n", otime);
12293     SendToProgram(message, cps);
12294 }
12295
12296 int
12297 BoolFeature(p, name, loc, cps)
12298      char **p;
12299      char *name;
12300      int *loc;
12301      ChessProgramState *cps;
12302 {
12303   char buf[MSG_SIZ];
12304   int len = strlen(name);
12305   int val;
12306   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12307     (*p) += len + 1;
12308     sscanf(*p, "%d", &val);
12309     *loc = (val != 0);
12310     while (**p && **p != ' ') (*p)++;
12311     sprintf(buf, "accepted %s\n", name);
12312     SendToProgram(buf, cps);
12313     return TRUE;
12314   }
12315   return FALSE;
12316 }
12317
12318 int
12319 IntFeature(p, name, loc, cps)
12320      char **p;
12321      char *name;
12322      int *loc;
12323      ChessProgramState *cps;
12324 {
12325   char buf[MSG_SIZ];
12326   int len = strlen(name);
12327   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12328     (*p) += len + 1;
12329     sscanf(*p, "%d", loc);
12330     while (**p && **p != ' ') (*p)++;
12331     sprintf(buf, "accepted %s\n", name);
12332     SendToProgram(buf, cps);
12333     return TRUE;
12334   }
12335   return FALSE;
12336 }
12337
12338 int
12339 StringFeature(p, name, loc, cps)
12340      char **p;
12341      char *name;
12342      char loc[];
12343      ChessProgramState *cps;
12344 {
12345   char buf[MSG_SIZ];
12346   int len = strlen(name);
12347   if (strncmp((*p), name, len) == 0
12348       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12349     (*p) += len + 2;
12350     sscanf(*p, "%[^\"]", loc);
12351     while (**p && **p != '\"') (*p)++;
12352     if (**p == '\"') (*p)++;
12353     sprintf(buf, "accepted %s\n", name);
12354     SendToProgram(buf, cps);
12355     return TRUE;
12356   }
12357   return FALSE;
12358 }
12359
12360 int 
12361 ParseOption(Option *opt, ChessProgramState *cps)
12362 // [HGM] options: process the string that defines an engine option, and determine
12363 // name, type, default value, and allowed value range
12364 {
12365         char *p, *q, buf[MSG_SIZ];
12366         int n, min = (-1)<<31, max = 1<<31, def;
12367
12368         if(p = strstr(opt->name, " -spin ")) {
12369             if((n = sscanf(p, " -spin %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;
12377         } else if((p = strstr(opt->name, " -slider "))) {
12378             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12379             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12380             if(max < min) max = min; // enforce consistency
12381             if(def < min) def = min;
12382             if(def > max) def = max;
12383             opt->value = def;
12384             opt->min = min;
12385             opt->max = max;
12386             opt->type = Spin; // Slider;
12387         } else if((p = strstr(opt->name, " -string "))) {
12388             opt->textValue = p+9;
12389             opt->type = TextBox;
12390         } else if((p = strstr(opt->name, " -file "))) {
12391             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12392             opt->textValue = p+7;
12393             opt->type = TextBox; // FileName;
12394         } else if((p = strstr(opt->name, " -path "))) {
12395             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12396             opt->textValue = p+7;
12397             opt->type = TextBox; // PathName;
12398         } else if(p = strstr(opt->name, " -check ")) {
12399             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12400             opt->value = (def != 0);
12401             opt->type = CheckBox;
12402         } else if(p = strstr(opt->name, " -combo ")) {
12403             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12404             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12405             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12406             opt->value = n = 0;
12407             while(q = StrStr(q, " /// ")) {
12408                 n++; *q = 0;    // count choices, and null-terminate each of them
12409                 q += 5;
12410                 if(*q == '*') { // remember default, which is marked with * prefix
12411                     q++;
12412                     opt->value = n;
12413                 }
12414                 cps->comboList[cps->comboCnt++] = q;
12415             }
12416             cps->comboList[cps->comboCnt++] = NULL;
12417             opt->max = n + 1;
12418             opt->type = ComboBox;
12419         } else if(p = strstr(opt->name, " -button")) {
12420             opt->type = Button;
12421         } else if(p = strstr(opt->name, " -save")) {
12422             opt->type = SaveButton;
12423         } else return FALSE;
12424         *p = 0; // terminate option name
12425         // now look if the command-line options define a setting for this engine option.
12426         if(cps->optionSettings && cps->optionSettings[0])
12427             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12428         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12429                 sprintf(buf, "option %s", p);
12430                 if(p = strstr(buf, ",")) *p = 0;
12431                 strcat(buf, "\n");
12432                 SendToProgram(buf, cps);
12433         }
12434         return TRUE;
12435 }
12436
12437 void
12438 FeatureDone(cps, val)
12439      ChessProgramState* cps;
12440      int val;
12441 {
12442   DelayedEventCallback cb = GetDelayedEvent();
12443   if ((cb == InitBackEnd3 && cps == &first) ||
12444       (cb == TwoMachinesEventIfReady && cps == &second)) {
12445     CancelDelayedEvent();
12446     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12447   }
12448   cps->initDone = val;
12449 }
12450
12451 /* Parse feature command from engine */
12452 void
12453 ParseFeatures(args, cps)
12454      char* args;
12455      ChessProgramState *cps;  
12456 {
12457   char *p = args;
12458   char *q;
12459   int val;
12460   char buf[MSG_SIZ];
12461
12462   for (;;) {
12463     while (*p == ' ') p++;
12464     if (*p == NULLCHAR) return;
12465
12466     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12467     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12468     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12469     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12470     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12471     if (BoolFeature(&p, "reuse", &val, cps)) {
12472       /* Engine can disable reuse, but can't enable it if user said no */
12473       if (!val) cps->reuse = FALSE;
12474       continue;
12475     }
12476     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12477     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12478       if (gameMode == TwoMachinesPlay) {
12479         DisplayTwoMachinesTitle();
12480       } else {
12481         DisplayTitle("");
12482       }
12483       continue;
12484     }
12485     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12486     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12487     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12488     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12489     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12490     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12491     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12492     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12493     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12494     if (IntFeature(&p, "done", &val, cps)) {
12495       FeatureDone(cps, val);
12496       continue;
12497     }
12498     /* Added by Tord: */
12499     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12500     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12501     /* End of additions by Tord */
12502
12503     /* [HGM] added features: */
12504     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12505     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12506     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12507     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12508     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12509     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12510     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12511         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12512             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12513             SendToProgram(buf, cps);
12514             continue;
12515         }
12516         if(cps->nrOptions >= MAX_OPTIONS) {
12517             cps->nrOptions--;
12518             sprintf(buf, "%s engine has too many options\n", cps->which);
12519             DisplayError(buf, 0);
12520         }
12521         continue;
12522     }
12523     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12524     /* End of additions by HGM */
12525
12526     /* unknown feature: complain and skip */
12527     q = p;
12528     while (*q && *q != '=') q++;
12529     sprintf(buf, "rejected %.*s\n", q-p, p);
12530     SendToProgram(buf, cps);
12531     p = q;
12532     if (*p == '=') {
12533       p++;
12534       if (*p == '\"') {
12535         p++;
12536         while (*p && *p != '\"') p++;
12537         if (*p == '\"') p++;
12538       } else {
12539         while (*p && *p != ' ') p++;
12540       }
12541     }
12542   }
12543
12544 }
12545
12546 void
12547 PeriodicUpdatesEvent(newState)
12548      int newState;
12549 {
12550     if (newState == appData.periodicUpdates)
12551       return;
12552
12553     appData.periodicUpdates=newState;
12554
12555     /* Display type changes, so update it now */
12556     DisplayAnalysis();
12557
12558     /* Get the ball rolling again... */
12559     if (newState) {
12560         AnalysisPeriodicEvent(1);
12561         StartAnalysisClock();
12562     }
12563 }
12564
12565 void
12566 PonderNextMoveEvent(newState)
12567      int newState;
12568 {
12569     if (newState == appData.ponderNextMove) return;
12570     if (gameMode == EditPosition) EditPositionDone();
12571     if (newState) {
12572         SendToProgram("hard\n", &first);
12573         if (gameMode == TwoMachinesPlay) {
12574             SendToProgram("hard\n", &second);
12575         }
12576     } else {
12577         SendToProgram("easy\n", &first);
12578         thinkOutput[0] = NULLCHAR;
12579         if (gameMode == TwoMachinesPlay) {
12580             SendToProgram("easy\n", &second);
12581         }
12582     }
12583     appData.ponderNextMove = newState;
12584 }
12585
12586 void
12587 NewSettingEvent(option, command, value)
12588      char *command;
12589      int option, value;
12590 {
12591     char buf[MSG_SIZ];
12592
12593     if (gameMode == EditPosition) EditPositionDone();
12594     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12595     SendToProgram(buf, &first);
12596     if (gameMode == TwoMachinesPlay) {
12597         SendToProgram(buf, &second);
12598     }
12599 }
12600
12601 void
12602 ShowThinkingEvent()
12603 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12604 {
12605     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12606     int newState = appData.showThinking
12607         // [HGM] thinking: other features now need thinking output as well
12608         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12609     
12610     if (oldState == newState) return;
12611     oldState = newState;
12612     if (gameMode == EditPosition) EditPositionDone();
12613     if (oldState) {
12614         SendToProgram("post\n", &first);
12615         if (gameMode == TwoMachinesPlay) {
12616             SendToProgram("post\n", &second);
12617         }
12618     } else {
12619         SendToProgram("nopost\n", &first);
12620         thinkOutput[0] = NULLCHAR;
12621         if (gameMode == TwoMachinesPlay) {
12622             SendToProgram("nopost\n", &second);
12623         }
12624     }
12625 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12626 }
12627
12628 void
12629 AskQuestionEvent(title, question, replyPrefix, which)
12630      char *title; char *question; char *replyPrefix; char *which;
12631 {
12632   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12633   if (pr == NoProc) return;
12634   AskQuestion(title, question, replyPrefix, pr);
12635 }
12636
12637 void
12638 DisplayMove(moveNumber)
12639      int moveNumber;
12640 {
12641     char message[MSG_SIZ];
12642     char res[MSG_SIZ];
12643     char cpThinkOutput[MSG_SIZ];
12644
12645     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12646     
12647     if (moveNumber == forwardMostMove - 1 || 
12648         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12649
12650         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12651
12652         if (strchr(cpThinkOutput, '\n')) {
12653             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12654         }
12655     } else {
12656         *cpThinkOutput = NULLCHAR;
12657     }
12658
12659     /* [AS] Hide thinking from human user */
12660     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12661         *cpThinkOutput = NULLCHAR;
12662         if( thinkOutput[0] != NULLCHAR ) {
12663             int i;
12664
12665             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12666                 cpThinkOutput[i] = '.';
12667             }
12668             cpThinkOutput[i] = NULLCHAR;
12669             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12670         }
12671     }
12672
12673     if (moveNumber == forwardMostMove - 1 &&
12674         gameInfo.resultDetails != NULL) {
12675         if (gameInfo.resultDetails[0] == NULLCHAR) {
12676             sprintf(res, " %s", PGNResult(gameInfo.result));
12677         } else {
12678             sprintf(res, " {%s} %s",
12679                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12680         }
12681     } else {
12682         res[0] = NULLCHAR;
12683     }
12684
12685     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12686         DisplayMessage(res, cpThinkOutput);
12687     } else {
12688         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12689                 WhiteOnMove(moveNumber) ? " " : ".. ",
12690                 parseList[moveNumber], res);
12691         DisplayMessage(message, cpThinkOutput);
12692     }
12693 }
12694
12695 void
12696 DisplayAnalysisText(text)
12697      char *text;
12698 {
12699   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12700       || appData.icsEngineAnalyze) 
12701     {
12702       EngineOutputPopUp();
12703     }
12704 }
12705
12706 static int
12707 only_one_move(str)
12708      char *str;
12709 {
12710     while (*str && isspace(*str)) ++str;
12711     while (*str && !isspace(*str)) ++str;
12712     if (!*str) return 1;
12713     while (*str && isspace(*str)) ++str;
12714     if (!*str) return 1;
12715     return 0;
12716 }
12717
12718 void
12719 DisplayAnalysis()
12720 {
12721     char buf[MSG_SIZ];
12722     char lst[MSG_SIZ / 2];
12723     double nps;
12724     static char *xtra[] = { "", " (--)", " (++)" };
12725     int h, m, s, cs;
12726   
12727     if (programStats.time == 0) {
12728         programStats.time = 1;
12729     }
12730   
12731     if (programStats.got_only_move) {
12732         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12733     } else {
12734         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12735
12736         nps = (u64ToDouble(programStats.nodes) /
12737              ((double)programStats.time /100.0));
12738
12739         cs = programStats.time % 100;
12740         s = programStats.time / 100;
12741         h = (s / (60*60));
12742         s = s - h*60*60;
12743         m = (s/60);
12744         s = s - m*60;
12745
12746         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12747           if (programStats.move_name[0] != NULLCHAR) {
12748             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12749                     programStats.depth,
12750                     programStats.nr_moves-programStats.moves_left,
12751                     programStats.nr_moves, programStats.move_name,
12752                     ((float)programStats.score)/100.0, lst,
12753                     only_one_move(lst)?
12754                     xtra[programStats.got_fail] : "",
12755                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12756           } else {
12757             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12758                     programStats.depth,
12759                     programStats.nr_moves-programStats.moves_left,
12760                     programStats.nr_moves, ((float)programStats.score)/100.0,
12761                     lst,
12762                     only_one_move(lst)?
12763                     xtra[programStats.got_fail] : "",
12764                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12765           }
12766         } else {
12767             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12768                     programStats.depth,
12769                     ((float)programStats.score)/100.0,
12770                     lst,
12771                     only_one_move(lst)?
12772                     xtra[programStats.got_fail] : "",
12773                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12774         }
12775     }
12776     DisplayAnalysisText(buf);
12777 }
12778
12779 void
12780 DisplayComment(moveNumber, text)
12781      int moveNumber;
12782      char *text;
12783 {
12784     char title[MSG_SIZ];
12785     char buf[8000]; // comment can be long!
12786     int score, depth;
12787
12788     if( appData.autoDisplayComment ) {
12789         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12790             strcpy(title, "Comment");
12791         } else {
12792             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12793                     WhiteOnMove(moveNumber) ? " " : ".. ",
12794                     parseList[moveNumber]);
12795         }
12796         // [HGM] PV info: display PV info together with (or as) comment
12797         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12798             if(text == NULL) text = "";                                           
12799             score = pvInfoList[moveNumber].score;
12800             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12801                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12802             text = buf;
12803         }
12804     } else title[0] = 0;
12805
12806     if (text != NULL)
12807         CommentPopUp(title, text);
12808 }
12809
12810 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12811  * might be busy thinking or pondering.  It can be omitted if your
12812  * gnuchess is configured to stop thinking immediately on any user
12813  * input.  However, that gnuchess feature depends on the FIONREAD
12814  * ioctl, which does not work properly on some flavors of Unix.
12815  */
12816 void
12817 Attention(cps)
12818      ChessProgramState *cps;
12819 {
12820 #if ATTENTION
12821     if (!cps->useSigint) return;
12822     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12823     switch (gameMode) {
12824       case MachinePlaysWhite:
12825       case MachinePlaysBlack:
12826       case TwoMachinesPlay:
12827       case IcsPlayingWhite:
12828       case IcsPlayingBlack:
12829       case AnalyzeMode:
12830       case AnalyzeFile:
12831         /* Skip if we know it isn't thinking */
12832         if (!cps->maybeThinking) return;
12833         if (appData.debugMode)
12834           fprintf(debugFP, "Interrupting %s\n", cps->which);
12835         InterruptChildProcess(cps->pr);
12836         cps->maybeThinking = FALSE;
12837         break;
12838       default:
12839         break;
12840     }
12841 #endif /*ATTENTION*/
12842 }
12843
12844 int
12845 CheckFlags()
12846 {
12847     if (whiteTimeRemaining <= 0) {
12848         if (!whiteFlag) {
12849             whiteFlag = TRUE;
12850             if (appData.icsActive) {
12851                 if (appData.autoCallFlag &&
12852                     gameMode == IcsPlayingBlack && !blackFlag) {
12853                   SendToICS(ics_prefix);
12854                   SendToICS("flag\n");
12855                 }
12856             } else {
12857                 if (blackFlag) {
12858                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12859                 } else {
12860                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12861                     if (appData.autoCallFlag) {
12862                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12863                         return TRUE;
12864                     }
12865                 }
12866             }
12867         }
12868     }
12869     if (blackTimeRemaining <= 0) {
12870         if (!blackFlag) {
12871             blackFlag = TRUE;
12872             if (appData.icsActive) {
12873                 if (appData.autoCallFlag &&
12874                     gameMode == IcsPlayingWhite && !whiteFlag) {
12875                   SendToICS(ics_prefix);
12876                   SendToICS("flag\n");
12877                 }
12878             } else {
12879                 if (whiteFlag) {
12880                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12881                 } else {
12882                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12883                     if (appData.autoCallFlag) {
12884                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12885                         return TRUE;
12886                     }
12887                 }
12888             }
12889         }
12890     }
12891     return FALSE;
12892 }
12893
12894 void
12895 CheckTimeControl()
12896 {
12897     if (!appData.clockMode || appData.icsActive ||
12898         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12899
12900     /*
12901      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12902      */
12903     if ( !WhiteOnMove(forwardMostMove) )
12904         /* White made time control */
12905         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12906         /* [HGM] time odds: correct new time quota for time odds! */
12907                                             / WhitePlayer()->timeOdds;
12908       else
12909         /* Black made time control */
12910         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12911                                             / WhitePlayer()->other->timeOdds;
12912 }
12913
12914 void
12915 DisplayBothClocks()
12916 {
12917     int wom = gameMode == EditPosition ?
12918       !blackPlaysFirst : WhiteOnMove(currentMove);
12919     DisplayWhiteClock(whiteTimeRemaining, wom);
12920     DisplayBlackClock(blackTimeRemaining, !wom);
12921 }
12922
12923
12924 /* Timekeeping seems to be a portability nightmare.  I think everyone
12925    has ftime(), but I'm really not sure, so I'm including some ifdefs
12926    to use other calls if you don't.  Clocks will be less accurate if
12927    you have neither ftime nor gettimeofday.
12928 */
12929
12930 /* VS 2008 requires the #include outside of the function */
12931 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12932 #include <sys/timeb.h>
12933 #endif
12934
12935 /* Get the current time as a TimeMark */
12936 void
12937 GetTimeMark(tm)
12938      TimeMark *tm;
12939 {
12940 #if HAVE_GETTIMEOFDAY
12941
12942     struct timeval timeVal;
12943     struct timezone timeZone;
12944
12945     gettimeofday(&timeVal, &timeZone);
12946     tm->sec = (long) timeVal.tv_sec; 
12947     tm->ms = (int) (timeVal.tv_usec / 1000L);
12948
12949 #else /*!HAVE_GETTIMEOFDAY*/
12950 #if HAVE_FTIME
12951
12952 // include <sys/timeb.h> / moved to just above start of function
12953     struct timeb timeB;
12954
12955     ftime(&timeB);
12956     tm->sec = (long) timeB.time;
12957     tm->ms = (int) timeB.millitm;
12958
12959 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12960     tm->sec = (long) time(NULL);
12961     tm->ms = 0;
12962 #endif
12963 #endif
12964 }
12965
12966 /* Return the difference in milliseconds between two
12967    time marks.  We assume the difference will fit in a long!
12968 */
12969 long
12970 SubtractTimeMarks(tm2, tm1)
12971      TimeMark *tm2, *tm1;
12972 {
12973     return 1000L*(tm2->sec - tm1->sec) +
12974            (long) (tm2->ms - tm1->ms);
12975 }
12976
12977
12978 /*
12979  * Code to manage the game clocks.
12980  *
12981  * In tournament play, black starts the clock and then white makes a move.
12982  * We give the human user a slight advantage if he is playing white---the
12983  * clocks don't run until he makes his first move, so it takes zero time.
12984  * Also, we don't account for network lag, so we could get out of sync
12985  * with GNU Chess's clock -- but then, referees are always right.  
12986  */
12987
12988 static TimeMark tickStartTM;
12989 static long intendedTickLength;
12990
12991 long
12992 NextTickLength(timeRemaining)
12993      long timeRemaining;
12994 {
12995     long nominalTickLength, nextTickLength;
12996
12997     if (timeRemaining > 0L && timeRemaining <= 10000L)
12998       nominalTickLength = 100L;
12999     else
13000       nominalTickLength = 1000L;
13001     nextTickLength = timeRemaining % nominalTickLength;
13002     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13003
13004     return nextTickLength;
13005 }
13006
13007 /* Adjust clock one minute up or down */
13008 void
13009 AdjustClock(Boolean which, int dir)
13010 {
13011     if(which) blackTimeRemaining += 60000*dir;
13012     else      whiteTimeRemaining += 60000*dir;
13013     DisplayBothClocks();
13014 }
13015
13016 /* Stop clocks and reset to a fresh time control */
13017 void
13018 ResetClocks() 
13019 {
13020     (void) StopClockTimer();
13021     if (appData.icsActive) {
13022         whiteTimeRemaining = blackTimeRemaining = 0;
13023     } else { /* [HGM] correct new time quote for time odds */
13024         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13025         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13026     }
13027     if (whiteFlag || blackFlag) {
13028         DisplayTitle("");
13029         whiteFlag = blackFlag = FALSE;
13030     }
13031     DisplayBothClocks();
13032 }
13033
13034 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13035
13036 /* Decrement running clock by amount of time that has passed */
13037 void
13038 DecrementClocks()
13039 {
13040     long timeRemaining;
13041     long lastTickLength, fudge;
13042     TimeMark now;
13043
13044     if (!appData.clockMode) return;
13045     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13046         
13047     GetTimeMark(&now);
13048
13049     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13050
13051     /* Fudge if we woke up a little too soon */
13052     fudge = intendedTickLength - lastTickLength;
13053     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13054
13055     if (WhiteOnMove(forwardMostMove)) {
13056         if(whiteNPS >= 0) lastTickLength = 0;
13057         timeRemaining = whiteTimeRemaining -= lastTickLength;
13058         DisplayWhiteClock(whiteTimeRemaining - fudge,
13059                           WhiteOnMove(currentMove));
13060     } else {
13061         if(blackNPS >= 0) lastTickLength = 0;
13062         timeRemaining = blackTimeRemaining -= lastTickLength;
13063         DisplayBlackClock(blackTimeRemaining - fudge,
13064                           !WhiteOnMove(currentMove));
13065     }
13066
13067     if (CheckFlags()) return;
13068         
13069     tickStartTM = now;
13070     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13071     StartClockTimer(intendedTickLength);
13072
13073     /* if the time remaining has fallen below the alarm threshold, sound the
13074      * alarm. if the alarm has sounded and (due to a takeback or time control
13075      * with increment) the time remaining has increased to a level above the
13076      * threshold, reset the alarm so it can sound again. 
13077      */
13078     
13079     if (appData.icsActive && appData.icsAlarm) {
13080
13081         /* make sure we are dealing with the user's clock */
13082         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13083                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13084            )) return;
13085
13086         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13087             alarmSounded = FALSE;
13088         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13089             PlayAlarmSound();
13090             alarmSounded = TRUE;
13091         }
13092     }
13093 }
13094
13095
13096 /* A player has just moved, so stop the previously running
13097    clock and (if in clock mode) start the other one.
13098    We redisplay both clocks in case we're in ICS mode, because
13099    ICS gives us an update to both clocks after every move.
13100    Note that this routine is called *after* forwardMostMove
13101    is updated, so the last fractional tick must be subtracted
13102    from the color that is *not* on move now.
13103 */
13104 void
13105 SwitchClocks()
13106 {
13107     long lastTickLength;
13108     TimeMark now;
13109     int flagged = FALSE;
13110
13111     GetTimeMark(&now);
13112
13113     if (StopClockTimer() && appData.clockMode) {
13114         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13115         if (WhiteOnMove(forwardMostMove)) {
13116             if(blackNPS >= 0) lastTickLength = 0;
13117             blackTimeRemaining -= lastTickLength;
13118            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13119 //         if(pvInfoList[forwardMostMove-1].time == -1)
13120                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13121                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13122         } else {
13123            if(whiteNPS >= 0) lastTickLength = 0;
13124            whiteTimeRemaining -= lastTickLength;
13125            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13126 //         if(pvInfoList[forwardMostMove-1].time == -1)
13127                  pvInfoList[forwardMostMove-1].time = 
13128                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13129         }
13130         flagged = CheckFlags();
13131     }
13132     CheckTimeControl();
13133
13134     if (flagged || !appData.clockMode) return;
13135
13136     switch (gameMode) {
13137       case MachinePlaysBlack:
13138       case MachinePlaysWhite:
13139       case BeginningOfGame:
13140         if (pausing) return;
13141         break;
13142
13143       case EditGame:
13144       case PlayFromGameFile:
13145       case IcsExamining:
13146         return;
13147
13148       default:
13149         break;
13150     }
13151
13152     tickStartTM = now;
13153     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13154       whiteTimeRemaining : blackTimeRemaining);
13155     StartClockTimer(intendedTickLength);
13156 }
13157         
13158
13159 /* Stop both clocks */
13160 void
13161 StopClocks()
13162 {       
13163     long lastTickLength;
13164     TimeMark now;
13165
13166     if (!StopClockTimer()) return;
13167     if (!appData.clockMode) return;
13168
13169     GetTimeMark(&now);
13170
13171     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13172     if (WhiteOnMove(forwardMostMove)) {
13173         if(whiteNPS >= 0) lastTickLength = 0;
13174         whiteTimeRemaining -= lastTickLength;
13175         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13176     } else {
13177         if(blackNPS >= 0) lastTickLength = 0;
13178         blackTimeRemaining -= lastTickLength;
13179         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13180     }
13181     CheckFlags();
13182 }
13183         
13184 /* Start clock of player on move.  Time may have been reset, so
13185    if clock is already running, stop and restart it. */
13186 void
13187 StartClocks()
13188 {
13189     (void) StopClockTimer(); /* in case it was running already */
13190     DisplayBothClocks();
13191     if (CheckFlags()) return;
13192
13193     if (!appData.clockMode) return;
13194     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13195
13196     GetTimeMark(&tickStartTM);
13197     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13198       whiteTimeRemaining : blackTimeRemaining);
13199
13200    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13201     whiteNPS = blackNPS = -1; 
13202     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13203        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13204         whiteNPS = first.nps;
13205     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13206        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13207         blackNPS = first.nps;
13208     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13209         whiteNPS = second.nps;
13210     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13211         blackNPS = second.nps;
13212     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13213
13214     StartClockTimer(intendedTickLength);
13215 }
13216
13217 char *
13218 TimeString(ms)
13219      long ms;
13220 {
13221     long second, minute, hour, day;
13222     char *sign = "";
13223     static char buf[32];
13224     
13225     if (ms > 0 && ms <= 9900) {
13226       /* convert milliseconds to tenths, rounding up */
13227       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13228
13229       sprintf(buf, " %03.1f ", tenths/10.0);
13230       return buf;
13231     }
13232
13233     /* convert milliseconds to seconds, rounding up */
13234     /* use floating point to avoid strangeness of integer division
13235        with negative dividends on many machines */
13236     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13237
13238     if (second < 0) {
13239         sign = "-";
13240         second = -second;
13241     }
13242     
13243     day = second / (60 * 60 * 24);
13244     second = second % (60 * 60 * 24);
13245     hour = second / (60 * 60);
13246     second = second % (60 * 60);
13247     minute = second / 60;
13248     second = second % 60;
13249     
13250     if (day > 0)
13251       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13252               sign, day, hour, minute, second);
13253     else if (hour > 0)
13254       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13255     else
13256       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13257     
13258     return buf;
13259 }
13260
13261
13262 /*
13263  * This is necessary because some C libraries aren't ANSI C compliant yet.
13264  */
13265 char *
13266 StrStr(string, match)
13267      char *string, *match;
13268 {
13269     int i, length;
13270     
13271     length = strlen(match);
13272     
13273     for (i = strlen(string) - length; i >= 0; i--, string++)
13274       if (!strncmp(match, string, length))
13275         return string;
13276     
13277     return NULL;
13278 }
13279
13280 char *
13281 StrCaseStr(string, match)
13282      char *string, *match;
13283 {
13284     int i, j, length;
13285     
13286     length = strlen(match);
13287     
13288     for (i = strlen(string) - length; i >= 0; i--, string++) {
13289         for (j = 0; j < length; j++) {
13290             if (ToLower(match[j]) != ToLower(string[j]))
13291               break;
13292         }
13293         if (j == length) return string;
13294     }
13295
13296     return NULL;
13297 }
13298
13299 #ifndef _amigados
13300 int
13301 StrCaseCmp(s1, s2)
13302      char *s1, *s2;
13303 {
13304     char c1, c2;
13305     
13306     for (;;) {
13307         c1 = ToLower(*s1++);
13308         c2 = ToLower(*s2++);
13309         if (c1 > c2) return 1;
13310         if (c1 < c2) return -1;
13311         if (c1 == NULLCHAR) return 0;
13312     }
13313 }
13314
13315
13316 int
13317 ToLower(c)
13318      int c;
13319 {
13320     return isupper(c) ? tolower(c) : c;
13321 }
13322
13323
13324 int
13325 ToUpper(c)
13326      int c;
13327 {
13328     return islower(c) ? toupper(c) : c;
13329 }
13330 #endif /* !_amigados    */
13331
13332 char *
13333 StrSave(s)
13334      char *s;
13335 {
13336     char *ret;
13337
13338     if ((ret = (char *) malloc(strlen(s) + 1))) {
13339         strcpy(ret, s);
13340     }
13341     return ret;
13342 }
13343
13344 char *
13345 StrSavePtr(s, savePtr)
13346      char *s, **savePtr;
13347 {
13348     if (*savePtr) {
13349         free(*savePtr);
13350     }
13351     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13352         strcpy(*savePtr, s);
13353     }
13354     return(*savePtr);
13355 }
13356
13357 char *
13358 PGNDate()
13359 {
13360     time_t clock;
13361     struct tm *tm;
13362     char buf[MSG_SIZ];
13363
13364     clock = time((time_t *)NULL);
13365     tm = localtime(&clock);
13366     sprintf(buf, "%04d.%02d.%02d",
13367             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13368     return StrSave(buf);
13369 }
13370
13371
13372 char *
13373 PositionToFEN(move, overrideCastling)
13374      int move;
13375      char *overrideCastling;
13376 {
13377     int i, j, fromX, fromY, toX, toY;
13378     int whiteToPlay;
13379     char buf[128];
13380     char *p, *q;
13381     int emptycount;
13382     ChessSquare piece;
13383
13384     whiteToPlay = (gameMode == EditPosition) ?
13385       !blackPlaysFirst : (move % 2 == 0);
13386     p = buf;
13387
13388     /* Piece placement data */
13389     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13390         emptycount = 0;
13391         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13392             if (boards[move][i][j] == EmptySquare) {
13393                 emptycount++;
13394             } else { ChessSquare piece = boards[move][i][j];
13395                 if (emptycount > 0) {
13396                     if(emptycount<10) /* [HGM] can be >= 10 */
13397                         *p++ = '0' + emptycount;
13398                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13399                     emptycount = 0;
13400                 }
13401                 if(PieceToChar(piece) == '+') {
13402                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13403                     *p++ = '+';
13404                     piece = (ChessSquare)(DEMOTED piece);
13405                 } 
13406                 *p++ = PieceToChar(piece);
13407                 if(p[-1] == '~') {
13408                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13409                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13410                     *p++ = '~';
13411                 }
13412             }
13413         }
13414         if (emptycount > 0) {
13415             if(emptycount<10) /* [HGM] can be >= 10 */
13416                 *p++ = '0' + emptycount;
13417             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13418             emptycount = 0;
13419         }
13420         *p++ = '/';
13421     }
13422     *(p - 1) = ' ';
13423
13424     /* [HGM] print Crazyhouse or Shogi holdings */
13425     if( gameInfo.holdingsWidth ) {
13426         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13427         q = p;
13428         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13429             piece = boards[move][i][BOARD_WIDTH-1];
13430             if( piece != EmptySquare )
13431               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13432                   *p++ = PieceToChar(piece);
13433         }
13434         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13435             piece = boards[move][BOARD_HEIGHT-i-1][0];
13436             if( piece != EmptySquare )
13437               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13438                   *p++ = PieceToChar(piece);
13439         }
13440
13441         if( q == p ) *p++ = '-';
13442         *p++ = ']';
13443         *p++ = ' ';
13444     }
13445
13446     /* Active color */
13447     *p++ = whiteToPlay ? 'w' : 'b';
13448     *p++ = ' ';
13449
13450   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13451     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13452   } else {
13453   if(nrCastlingRights) {
13454      q = p;
13455      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13456        /* [HGM] write directly from rights */
13457            if(castlingRights[move][2] >= 0 &&
13458               castlingRights[move][0] >= 0   )
13459                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13460            if(castlingRights[move][2] >= 0 &&
13461               castlingRights[move][1] >= 0   )
13462                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13463            if(castlingRights[move][5] >= 0 &&
13464               castlingRights[move][3] >= 0   )
13465                 *p++ = castlingRights[move][3] + AAA;
13466            if(castlingRights[move][5] >= 0 &&
13467               castlingRights[move][4] >= 0   )
13468                 *p++ = castlingRights[move][4] + AAA;
13469      } else {
13470
13471         /* [HGM] write true castling rights */
13472         if( nrCastlingRights == 6 ) {
13473             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13474                castlingRights[move][2] >= 0  ) *p++ = 'K';
13475             if(castlingRights[move][1] == BOARD_LEFT &&
13476                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13477             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13478                castlingRights[move][5] >= 0  ) *p++ = 'k';
13479             if(castlingRights[move][4] == BOARD_LEFT &&
13480                castlingRights[move][5] >= 0  ) *p++ = 'q';
13481         }
13482      }
13483      if (q == p) *p++ = '-'; /* No castling rights */
13484      *p++ = ' ';
13485   }
13486
13487   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13488      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13489     /* En passant target square */
13490     if (move > backwardMostMove) {
13491         fromX = moveList[move - 1][0] - AAA;
13492         fromY = moveList[move - 1][1] - ONE;
13493         toX = moveList[move - 1][2] - AAA;
13494         toY = moveList[move - 1][3] - ONE;
13495         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13496             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13497             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13498             fromX == toX) {
13499             /* 2-square pawn move just happened */
13500             *p++ = toX + AAA;
13501             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13502         } else {
13503             *p++ = '-';
13504         }
13505     } else if(move == backwardMostMove) {
13506         // [HGM] perhaps we should always do it like this, and forget the above?
13507         if(epStatus[move] >= 0) {
13508             *p++ = epStatus[move] + AAA;
13509             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13510         } else {
13511             *p++ = '-';
13512         }
13513     } else {
13514         *p++ = '-';
13515     }
13516     *p++ = ' ';
13517   }
13518   }
13519
13520     /* [HGM] find reversible plies */
13521     {   int i = 0, j=move;
13522
13523         if (appData.debugMode) { int k;
13524             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13525             for(k=backwardMostMove; k<=forwardMostMove; k++)
13526                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13527
13528         }
13529
13530         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13531         if( j == backwardMostMove ) i += initialRulePlies;
13532         sprintf(p, "%d ", i);
13533         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13534     }
13535     /* Fullmove number */
13536     sprintf(p, "%d", (move / 2) + 1);
13537     
13538     return StrSave(buf);
13539 }
13540
13541 Boolean
13542 ParseFEN(board, blackPlaysFirst, fen)
13543     Board board;
13544      int *blackPlaysFirst;
13545      char *fen;
13546 {
13547     int i, j;
13548     char *p;
13549     int emptycount;
13550     ChessSquare piece;
13551
13552     p = fen;
13553
13554     /* [HGM] by default clear Crazyhouse holdings, if present */
13555     if(gameInfo.holdingsWidth) {
13556        for(i=0; i<BOARD_HEIGHT; i++) {
13557            board[i][0]             = EmptySquare; /* black holdings */
13558            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13559            board[i][1]             = (ChessSquare) 0; /* black counts */
13560            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13561        }
13562     }
13563
13564     /* Piece placement data */
13565     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13566         j = 0;
13567         for (;;) {
13568             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13569                 if (*p == '/') p++;
13570                 emptycount = gameInfo.boardWidth - j;
13571                 while (emptycount--)
13572                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13573                 break;
13574 #if(BOARD_SIZE >= 10)
13575             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13576                 p++; emptycount=10;
13577                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13578                 while (emptycount--)
13579                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13580 #endif
13581             } else if (isdigit(*p)) {
13582                 emptycount = *p++ - '0';
13583                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13584                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13585                 while (emptycount--)
13586                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13587             } else if (*p == '+' || isalpha(*p)) {
13588                 if (j >= gameInfo.boardWidth) return FALSE;
13589                 if(*p=='+') {
13590                     piece = CharToPiece(*++p);
13591                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13592                     piece = (ChessSquare) (PROMOTED piece ); p++;
13593                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13594                 } else piece = CharToPiece(*p++);
13595
13596                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13597                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13598                     piece = (ChessSquare) (PROMOTED piece);
13599                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13600                     p++;
13601                 }
13602                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13603             } else {
13604                 return FALSE;
13605             }
13606         }
13607     }
13608     while (*p == '/' || *p == ' ') p++;
13609
13610     /* [HGM] look for Crazyhouse holdings here */
13611     while(*p==' ') p++;
13612     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13613         if(*p == '[') p++;
13614         if(*p == '-' ) *p++; /* empty holdings */ else {
13615             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13616             /* if we would allow FEN reading to set board size, we would   */
13617             /* have to add holdings and shift the board read so far here   */
13618             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13619                 *p++;
13620                 if((int) piece >= (int) BlackPawn ) {
13621                     i = (int)piece - (int)BlackPawn;
13622                     i = PieceToNumber((ChessSquare)i);
13623                     if( i >= gameInfo.holdingsSize ) return FALSE;
13624                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13625                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13626                 } else {
13627                     i = (int)piece - (int)WhitePawn;
13628                     i = PieceToNumber((ChessSquare)i);
13629                     if( i >= gameInfo.holdingsSize ) return FALSE;
13630                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13631                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13632                 }
13633             }
13634         }
13635         if(*p == ']') *p++;
13636     }
13637
13638     while(*p == ' ') p++;
13639
13640     /* Active color */
13641     switch (*p++) {
13642       case 'w':
13643         *blackPlaysFirst = FALSE;
13644         break;
13645       case 'b': 
13646         *blackPlaysFirst = TRUE;
13647         break;
13648       default:
13649         return FALSE;
13650     }
13651
13652     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13653     /* return the extra info in global variiables             */
13654
13655     /* set defaults in case FEN is incomplete */
13656     FENepStatus = EP_UNKNOWN;
13657     for(i=0; i<nrCastlingRights; i++ ) {
13658         FENcastlingRights[i] =
13659             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13660     }   /* assume possible unless obviously impossible */
13661     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13662     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13663     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13664     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13665     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13666     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13667     FENrulePlies = 0;
13668
13669     while(*p==' ') p++;
13670     if(nrCastlingRights) {
13671       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13672           /* castling indicator present, so default becomes no castlings */
13673           for(i=0; i<nrCastlingRights; i++ ) {
13674                  FENcastlingRights[i] = -1;
13675           }
13676       }
13677       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13678              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13679              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13680              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13681         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13682
13683         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13684             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13685             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13686         }
13687         switch(c) {
13688           case'K':
13689               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13690               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13691               FENcastlingRights[2] = whiteKingFile;
13692               break;
13693           case'Q':
13694               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13695               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13696               FENcastlingRights[2] = whiteKingFile;
13697               break;
13698           case'k':
13699               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13700               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13701               FENcastlingRights[5] = blackKingFile;
13702               break;
13703           case'q':
13704               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13705               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13706               FENcastlingRights[5] = blackKingFile;
13707           case '-':
13708               break;
13709           default: /* FRC castlings */
13710               if(c >= 'a') { /* black rights */
13711                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13712                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13713                   if(i == BOARD_RGHT) break;
13714                   FENcastlingRights[5] = i;
13715                   c -= AAA;
13716                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13717                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13718                   if(c > i)
13719                       FENcastlingRights[3] = c;
13720                   else
13721                       FENcastlingRights[4] = c;
13722               } else { /* white rights */
13723                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13724                     if(board[0][i] == WhiteKing) break;
13725                   if(i == BOARD_RGHT) break;
13726                   FENcastlingRights[2] = i;
13727                   c -= AAA - 'a' + 'A';
13728                   if(board[0][c] >= WhiteKing) break;
13729                   if(c > i)
13730                       FENcastlingRights[0] = c;
13731                   else
13732                       FENcastlingRights[1] = c;
13733               }
13734         }
13735       }
13736     if (appData.debugMode) {
13737         fprintf(debugFP, "FEN castling rights:");
13738         for(i=0; i<nrCastlingRights; i++)
13739         fprintf(debugFP, " %d", FENcastlingRights[i]);
13740         fprintf(debugFP, "\n");
13741     }
13742
13743       while(*p==' ') p++;
13744     }
13745
13746     /* read e.p. field in games that know e.p. capture */
13747     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13748        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13749       if(*p=='-') {
13750         p++; FENepStatus = EP_NONE;
13751       } else {
13752          char c = *p++ - AAA;
13753
13754          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13755          if(*p >= '0' && *p <='9') *p++;
13756          FENepStatus = c;
13757       }
13758     }
13759
13760
13761     if(sscanf(p, "%d", &i) == 1) {
13762         FENrulePlies = i; /* 50-move ply counter */
13763         /* (The move number is still ignored)    */
13764     }
13765
13766     return TRUE;
13767 }
13768       
13769 void
13770 EditPositionPasteFEN(char *fen)
13771 {
13772   if (fen != NULL) {
13773     Board initial_position;
13774
13775     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13776       DisplayError(_("Bad FEN position in clipboard"), 0);
13777       return ;
13778     } else {
13779       int savedBlackPlaysFirst = blackPlaysFirst;
13780       EditPositionEvent();
13781       blackPlaysFirst = savedBlackPlaysFirst;
13782       CopyBoard(boards[0], initial_position);
13783           /* [HGM] copy FEN attributes as well */
13784           {   int i;
13785               initialRulePlies = FENrulePlies;
13786               epStatus[0] = FENepStatus;
13787               for( i=0; i<nrCastlingRights; i++ )
13788                   castlingRights[0][i] = FENcastlingRights[i];
13789           }
13790       EditPositionDone();
13791       DisplayBothClocks();
13792       DrawPosition(FALSE, boards[currentMove]);
13793     }
13794   }
13795 }