Display PV on right-clicking board
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
239 int endPV = -1;
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
243 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252
253 /* States for ics_getting_history */
254 #define H_FALSE 0
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
260
261 /* whosays values for GameEnds */
262 #define GE_ICS 0
263 #define GE_ENGINE 1
264 #define GE_PLAYER 2
265 #define GE_FILE 3
266 #define GE_XBOARD 4
267 #define GE_ENGINE1 5
268 #define GE_ENGINE2 6
269
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
272
273 /* Different types of move when calling RegisterMove */
274 #define CMAIL_MOVE   0
275 #define CMAIL_RESIGN 1
276 #define CMAIL_DRAW   2
277 #define CMAIL_ACCEPT 3
278
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
283
284 /* Telnet protocol constants */
285 #define TN_WILL 0373
286 #define TN_WONT 0374
287 #define TN_DO   0375
288 #define TN_DONT 0376
289 #define TN_IAC  0377
290 #define TN_ECHO 0001
291 #define TN_SGA  0003
292 #define TN_PORT 23
293
294 /* [AS] */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
296 {
297     assert( dst != NULL );
298     assert( src != NULL );
299     assert( count > 0 );
300
301     strncpy( dst, src, count );
302     dst[ count-1 ] = '\0';
303     return dst;
304 }
305
306 /* Some compiler can't cast u64 to double
307  * This function do the job for us:
308
309  * We use the highest bit for cast, this only
310  * works if the highest bit is not
311  * in use (This should not happen)
312  *
313  * We used this for all compiler
314  */
315 double
316 u64ToDouble(u64 value)
317 {
318   double r;
319   u64 tmp = value & u64Const(0x7fffffffffffffff);
320   r = (double)(s64)tmp;
321   if (value & u64Const(0x8000000000000000))
322        r +=  9.2233720368547758080e18; /* 2^63 */
323  return r;
324 }
325
326 /* Fake up flags for now, as we aren't keeping track of castling
327    availability yet. [HGM] Change of logic: the flag now only
328    indicates the type of castlings allowed by the rule of the game.
329    The actual rights themselves are maintained in the array
330    castlingRights, as part of the game history, and are not probed
331    by this function.
332  */
333 int
334 PosFlags(index)
335 {
336   int flags = F_ALL_CASTLE_OK;
337   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338   switch (gameInfo.variant) {
339   case VariantSuicide:
340     flags &= ~F_ALL_CASTLE_OK;
341   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342     flags |= F_IGNORE_CHECK;
343   case VariantLosers:
344     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345     break;
346   case VariantAtomic:
347     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
348     break;
349   case VariantKriegspiel:
350     flags |= F_KRIEGSPIEL_CAPTURE;
351     break;
352   case VariantCapaRandom: 
353   case VariantFischeRandom:
354     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355   case VariantNoCastle:
356   case VariantShatranj:
357   case VariantCourier:
358     flags &= ~F_ALL_CASTLE_OK;
359     break;
360   default:
361     break;
362   }
363   return flags;
364 }
365
366 FILE *gameFileFP, *debugFP;
367
368 /* 
369     [AS] Note: sometimes, the sscanf() function is used to parse the input
370     into a fixed-size buffer. Because of this, we must be prepared to
371     receive strings as long as the size of the input buffer, which is currently
372     set to 4K for Windows and 8K for the rest.
373     So, we must either allocate sufficiently large buffers here, or
374     reduce the size of the input buffer in the input reading part.
375 */
376
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
379 char thinkOutput1[MSG_SIZ*10];
380
381 ChessProgramState first, second;
382
383 /* premove variables */
384 int premoveToX = 0;
385 int premoveToY = 0;
386 int premoveFromX = 0;
387 int premoveFromY = 0;
388 int premovePromoChar = 0;
389 int gotPremove = 0;
390 Boolean alarmSounded;
391 /* end premove variables */
392
393 char *ics_prefix = "$";
394 int ics_type = ICS_GENERIC;
395
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
397 int pauseExamForwardMostMove = 0;
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
404 int whiteFlag = FALSE, blackFlag = FALSE;
405 int userOfferedDraw = FALSE;
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
408 int cmailMoveType[CMAIL_MAX_GAMES];
409 long ics_clock_paused = 0;
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
412 GameMode gameMode = BeginningOfGame;
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
416 int hiddenThinkOutputState = 0; /* [AS] */
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
418 int adjudicateLossPlies = 6;
419 char white_holding[64], black_holding[64];
420 TimeMark lastNodeCountTime;
421 long lastNodeCount=0;
422 int have_sent_ICS_logon = 0;
423 int movesPerSession;
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
425 long timeControl_2; /* [AS] Allow separate time controls */
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
427 long timeRemaining[2][MAX_MOVES];
428 int matchGame = 0;
429 TimeMark programStartTime;
430 char ics_handle[MSG_SIZ];
431 int have_set_title = 0;
432
433 /* animateTraining preserves the state of appData.animate
434  * when Training mode is activated. This allows the
435  * response to be animated when appData.animate == TRUE and
436  * appData.animateDragging == TRUE.
437  */
438 Boolean animateTraining;
439
440 GameInfo gameInfo;
441
442 AppData appData;
443
444 Board boards[MAX_MOVES];
445 /* [HGM] Following 7 needed for accurate legality tests: */
446 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
447 signed char  initialRights[BOARD_FILES];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
451 int loadFlag = 0; 
452 int shuffleOpenings;
453 int mute; // mute all sounds
454
455 // [HGM] vari: next 12 to save and restore variations
456 #define MAX_VARIATIONS 10
457 int framePtr = MAX_MOVES-1; // points to free stack entry
458 int storedGames = 0;
459 int savedFirst[MAX_VARIATIONS];
460 int savedLast[MAX_VARIATIONS];
461 int savedFramePtr[MAX_VARIATIONS];
462 char *savedDetails[MAX_VARIATIONS];
463 ChessMove savedResult[MAX_VARIATIONS];
464
465 void PushTail P((int firstMove, int lastMove));
466 Boolean PopTail P((Boolean annotate));
467 void CleanupTail P((void));
468
469 ChessSquare  FIDEArray[2][BOARD_FILES] = {
470     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
471         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
472     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
473         BlackKing, BlackBishop, BlackKnight, BlackRook }
474 };
475
476 ChessSquare twoKingsArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackKing, BlackKnight, BlackRook }
481 };
482
483 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
485         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
486     { BlackRook, BlackMan, BlackBishop, BlackQueen,
487         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
488 };
489
490 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
491     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
492         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
493     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
494         BlackKing, BlackBishop, BlackKnight, BlackRook }
495 };
496
497 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
498     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
499         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
500     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
501         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
502 };
503
504
505 #if (BOARD_FILES>=10)
506 ChessSquare ShogiArray[2][BOARD_FILES] = {
507     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
508         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
509     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
510         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
511 };
512
513 ChessSquare XiangqiArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
515         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
517         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
518 };
519
520 ChessSquare CapablancaArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
522         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
524         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
525 };
526
527 ChessSquare GreatArray[2][BOARD_FILES] = {
528     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
529         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
530     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
531         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
532 };
533
534 ChessSquare JanusArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
536         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
537     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
538         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
539 };
540
541 #ifdef GOTHIC
542 ChessSquare GothicArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
544         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
546         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
547 };
548 #else // !GOTHIC
549 #define GothicArray CapablancaArray
550 #endif // !GOTHIC
551
552 #ifdef FALCON
553 ChessSquare FalconArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
555         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
557         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
558 };
559 #else // !FALCON
560 #define FalconArray CapablancaArray
561 #endif // !FALCON
562
563 #else // !(BOARD_FILES>=10)
564 #define XiangqiPosition FIDEArray
565 #define CapablancaArray FIDEArray
566 #define GothicArray FIDEArray
567 #define GreatArray FIDEArray
568 #endif // !(BOARD_FILES>=10)
569
570 #if (BOARD_FILES>=12)
571 ChessSquare CourierArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
573         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
575         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
576 };
577 #else // !(BOARD_FILES>=12)
578 #define CourierArray CapablancaArray
579 #endif // !(BOARD_FILES>=12)
580
581
582 Board initialPosition;
583
584
585 /* Convert str to a rating. Checks for special cases of "----",
586
587    "++++", etc. Also strips ()'s */
588 int
589 string_to_rating(str)
590   char *str;
591 {
592   while(*str && !isdigit(*str)) ++str;
593   if (!*str)
594     return 0;   /* One of the special "no rating" cases */
595   else
596     return atoi(str);
597 }
598
599 void
600 ClearProgramStats()
601 {
602     /* Init programStats */
603     programStats.movelist[0] = 0;
604     programStats.depth = 0;
605     programStats.nr_moves = 0;
606     programStats.moves_left = 0;
607     programStats.nodes = 0;
608     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
609     programStats.score = 0;
610     programStats.got_only_move = 0;
611     programStats.got_fail = 0;
612     programStats.line_is_book = 0;
613 }
614
615 void
616 InitBackEnd1()
617 {
618     int matched, min, sec;
619
620     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
621
622     GetTimeMark(&programStartTime);
623     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
624
625     ClearProgramStats();
626     programStats.ok_to_send = 1;
627     programStats.seen_stat = 0;
628
629     /*
630      * Initialize game list
631      */
632     ListNew(&gameList);
633
634
635     /*
636      * Internet chess server status
637      */
638     if (appData.icsActive) {
639         appData.matchMode = FALSE;
640         appData.matchGames = 0;
641 #if ZIPPY       
642         appData.noChessProgram = !appData.zippyPlay;
643 #else
644         appData.zippyPlay = FALSE;
645         appData.zippyTalk = FALSE;
646         appData.noChessProgram = TRUE;
647 #endif
648         if (*appData.icsHelper != NULLCHAR) {
649             appData.useTelnet = TRUE;
650             appData.telnetProgram = appData.icsHelper;
651         }
652     } else {
653         appData.zippyTalk = appData.zippyPlay = FALSE;
654     }
655
656     /* [AS] Initialize pv info list [HGM] and game state */
657     {
658         int i, j;
659
660         for( i=0; i<=framePtr; i++ ) {
661             pvInfoList[i].depth = -1;
662             boards[i][EP_STATUS] = EP_NONE;
663             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
664         }
665     }
666
667     /*
668      * Parse timeControl resource
669      */
670     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
671                           appData.movesPerSession)) {
672         char buf[MSG_SIZ];
673         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
674         DisplayFatalError(buf, 0, 2);
675     }
676
677     /*
678      * Parse searchTime resource
679      */
680     if (*appData.searchTime != NULLCHAR) {
681         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
682         if (matched == 1) {
683             searchTime = min * 60;
684         } else if (matched == 2) {
685             searchTime = min * 60 + sec;
686         } else {
687             char buf[MSG_SIZ];
688             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
689             DisplayFatalError(buf, 0, 2);
690         }
691     }
692
693     /* [AS] Adjudication threshold */
694     adjudicateLossThreshold = appData.adjudicateLossThreshold;
695     
696     first.which = "first";
697     second.which = "second";
698     first.maybeThinking = second.maybeThinking = FALSE;
699     first.pr = second.pr = NoProc;
700     first.isr = second.isr = NULL;
701     first.sendTime = second.sendTime = 2;
702     first.sendDrawOffers = 1;
703     if (appData.firstPlaysBlack) {
704         first.twoMachinesColor = "black\n";
705         second.twoMachinesColor = "white\n";
706     } else {
707         first.twoMachinesColor = "white\n";
708         second.twoMachinesColor = "black\n";
709     }
710     first.program = appData.firstChessProgram;
711     second.program = appData.secondChessProgram;
712     first.host = appData.firstHost;
713     second.host = appData.secondHost;
714     first.dir = appData.firstDirectory;
715     second.dir = appData.secondDirectory;
716     first.other = &second;
717     second.other = &first;
718     first.initString = appData.initString;
719     second.initString = appData.secondInitString;
720     first.computerString = appData.firstComputerString;
721     second.computerString = appData.secondComputerString;
722     first.useSigint = second.useSigint = TRUE;
723     first.useSigterm = second.useSigterm = TRUE;
724     first.reuse = appData.reuseFirst;
725     second.reuse = appData.reuseSecond;
726     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
727     second.nps = appData.secondNPS;
728     first.useSetboard = second.useSetboard = FALSE;
729     first.useSAN = second.useSAN = FALSE;
730     first.usePing = second.usePing = FALSE;
731     first.lastPing = second.lastPing = 0;
732     first.lastPong = second.lastPong = 0;
733     first.usePlayother = second.usePlayother = FALSE;
734     first.useColors = second.useColors = TRUE;
735     first.useUsermove = second.useUsermove = FALSE;
736     first.sendICS = second.sendICS = FALSE;
737     first.sendName = second.sendName = appData.icsActive;
738     first.sdKludge = second.sdKludge = FALSE;
739     first.stKludge = second.stKludge = FALSE;
740     TidyProgramName(first.program, first.host, first.tidy);
741     TidyProgramName(second.program, second.host, second.tidy);
742     first.matchWins = second.matchWins = 0;
743     strcpy(first.variants, appData.variant);
744     strcpy(second.variants, appData.variant);
745     first.analysisSupport = second.analysisSupport = 2; /* detect */
746     first.analyzing = second.analyzing = FALSE;
747     first.initDone = second.initDone = FALSE;
748
749     /* New features added by Tord: */
750     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
751     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
752     /* End of new features added by Tord. */
753     first.fenOverride  = appData.fenOverride1;
754     second.fenOverride = appData.fenOverride2;
755
756     /* [HGM] time odds: set factor for each machine */
757     first.timeOdds  = appData.firstTimeOdds;
758     second.timeOdds = appData.secondTimeOdds;
759     { int norm = 1;
760         if(appData.timeOddsMode) {
761             norm = first.timeOdds;
762             if(norm > second.timeOdds) norm = second.timeOdds;
763         }
764         first.timeOdds /= norm;
765         second.timeOdds /= norm;
766     }
767
768     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
769     first.accumulateTC = appData.firstAccumulateTC;
770     second.accumulateTC = appData.secondAccumulateTC;
771     first.maxNrOfSessions = second.maxNrOfSessions = 1;
772
773     /* [HGM] debug */
774     first.debug = second.debug = FALSE;
775     first.supportsNPS = second.supportsNPS = UNKNOWN;
776
777     /* [HGM] options */
778     first.optionSettings  = appData.firstOptions;
779     second.optionSettings = appData.secondOptions;
780
781     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
782     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
783     first.isUCI = appData.firstIsUCI; /* [AS] */
784     second.isUCI = appData.secondIsUCI; /* [AS] */
785     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
786     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
787
788     if (appData.firstProtocolVersion > PROTOVER ||
789         appData.firstProtocolVersion < 1) {
790       char buf[MSG_SIZ];
791       sprintf(buf, _("protocol version %d not supported"),
792               appData.firstProtocolVersion);
793       DisplayFatalError(buf, 0, 2);
794     } else {
795       first.protocolVersion = appData.firstProtocolVersion;
796     }
797
798     if (appData.secondProtocolVersion > PROTOVER ||
799         appData.secondProtocolVersion < 1) {
800       char buf[MSG_SIZ];
801       sprintf(buf, _("protocol version %d not supported"),
802               appData.secondProtocolVersion);
803       DisplayFatalError(buf, 0, 2);
804     } else {
805       second.protocolVersion = appData.secondProtocolVersion;
806     }
807
808     if (appData.icsActive) {
809         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
810 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
811     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
812         appData.clockMode = FALSE;
813         first.sendTime = second.sendTime = 0;
814     }
815     
816 #if ZIPPY
817     /* Override some settings from environment variables, for backward
818        compatibility.  Unfortunately it's not feasible to have the env
819        vars just set defaults, at least in xboard.  Ugh.
820     */
821     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822       ZippyInit();
823     }
824 #endif
825     
826     if (appData.noChessProgram) {
827         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828         sprintf(programVersion, "%s", PACKAGE_STRING);
829     } else {
830       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
831       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
832       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
833     }
834
835     if (!appData.icsActive) {
836       char buf[MSG_SIZ];
837       /* Check for variants that are supported only in ICS mode,
838          or not at all.  Some that are accepted here nevertheless
839          have bugs; see comments below.
840       */
841       VariantClass variant = StringToVariant(appData.variant);
842       switch (variant) {
843       case VariantBughouse:     /* need four players and two boards */
844       case VariantKriegspiel:   /* need to hide pieces and move details */
845       /* case VariantFischeRandom: (Fabien: moved below) */
846         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
847         DisplayFatalError(buf, 0, 2);
848         return;
849
850       case VariantUnknown:
851       case VariantLoadable:
852       case Variant29:
853       case Variant30:
854       case Variant31:
855       case Variant32:
856       case Variant33:
857       case Variant34:
858       case Variant35:
859       case Variant36:
860       default:
861         sprintf(buf, _("Unknown variant name %s"), appData.variant);
862         DisplayFatalError(buf, 0, 2);
863         return;
864
865       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
866       case VariantFairy:      /* [HGM] TestLegality definitely off! */
867       case VariantGothic:     /* [HGM] should work */
868       case VariantCapablanca: /* [HGM] should work */
869       case VariantCourier:    /* [HGM] initial forced moves not implemented */
870       case VariantShogi:      /* [HGM] drops not tested for legality */
871       case VariantKnightmate: /* [HGM] should work */
872       case VariantCylinder:   /* [HGM] untested */
873       case VariantFalcon:     /* [HGM] untested */
874       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
875                                  offboard interposition not understood */
876       case VariantNormal:     /* definitely works! */
877       case VariantWildCastle: /* pieces not automatically shuffled */
878       case VariantNoCastle:   /* pieces not automatically shuffled */
879       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
880       case VariantLosers:     /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantSuicide:    /* should work except for win condition,
883                                  and doesn't know captures are mandatory */
884       case VariantGiveaway:   /* should work except for win condition,
885                                  and doesn't know captures are mandatory */
886       case VariantTwoKings:   /* should work */
887       case VariantAtomic:     /* should work except for win condition */
888       case Variant3Check:     /* should work except for win condition */
889       case VariantShatranj:   /* should work except for all win conditions */
890       case VariantBerolina:   /* might work if TestLegality is off */
891       case VariantCapaRandom: /* should work */
892       case VariantJanus:      /* should work */
893       case VariantSuper:      /* experimental */
894       case VariantGreat:      /* experimental, requires legality testing to be off */
895         break;
896       }
897     }
898
899     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
900     InitEngineUCI( installDir, &second );
901 }
902
903 int NextIntegerFromString( char ** str, long * value )
904 {
905     int result = -1;
906     char * s = *str;
907
908     while( *s == ' ' || *s == '\t' ) {
909         s++;
910     }
911
912     *value = 0;
913
914     if( *s >= '0' && *s <= '9' ) {
915         while( *s >= '0' && *s <= '9' ) {
916             *value = *value * 10 + (*s - '0');
917             s++;
918         }
919
920         result = 0;
921     }
922
923     *str = s;
924
925     return result;
926 }
927
928 int NextTimeControlFromString( char ** str, long * value )
929 {
930     long temp;
931     int result = NextIntegerFromString( str, &temp );
932
933     if( result == 0 ) {
934         *value = temp * 60; /* Minutes */
935         if( **str == ':' ) {
936             (*str)++;
937             result = NextIntegerFromString( str, &temp );
938             *value += temp; /* Seconds */
939         }
940     }
941
942     return result;
943 }
944
945 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
946 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
947     int result = -1; long temp, temp2;
948
949     if(**str != '+') return -1; // old params remain in force!
950     (*str)++;
951     if( NextTimeControlFromString( str, &temp ) ) return -1;
952
953     if(**str != '/') {
954         /* time only: incremental or sudden-death time control */
955         if(**str == '+') { /* increment follows; read it */
956             (*str)++;
957             if(result = NextIntegerFromString( str, &temp2)) return -1;
958             *inc = temp2 * 1000;
959         } else *inc = 0;
960         *moves = 0; *tc = temp * 1000; 
961         return 0;
962     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
963
964     (*str)++; /* classical time control */
965     result = NextTimeControlFromString( str, &temp2);
966     if(result == 0) {
967         *moves = temp/60;
968         *tc    = temp2 * 1000;
969         *inc   = 0;
970     }
971     return result;
972 }
973
974 int GetTimeQuota(int movenr)
975 {   /* [HGM] get time to add from the multi-session time-control string */
976     int moves=1; /* kludge to force reading of first session */
977     long time, increment;
978     char *s = fullTimeControlString;
979
980     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
981     do {
982         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
983         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
984         if(movenr == -1) return time;    /* last move before new session     */
985         if(!moves) return increment;     /* current session is incremental   */
986         if(movenr >= 0) movenr -= moves; /* we already finished this session */
987     } while(movenr >= -1);               /* try again for next session       */
988
989     return 0; // no new time quota on this move
990 }
991
992 int
993 ParseTimeControl(tc, ti, mps)
994      char *tc;
995      int ti;
996      int mps;
997 {
998   long tc1;
999   long tc2;
1000   char buf[MSG_SIZ];
1001   
1002   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1003   if(ti > 0) {
1004     if(mps)
1005       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1006     else sprintf(buf, "+%s+%d", tc, ti);
1007   } else {
1008     if(mps)
1009              sprintf(buf, "+%d/%s", mps, tc);
1010     else sprintf(buf, "+%s", tc);
1011   }
1012   fullTimeControlString = StrSave(buf);
1013   
1014   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1015     return FALSE;
1016   }
1017   
1018   if( *tc == '/' ) {
1019     /* Parse second time control */
1020     tc++;
1021     
1022     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1023       return FALSE;
1024     }
1025     
1026     if( tc2 == 0 ) {
1027       return FALSE;
1028     }
1029     
1030     timeControl_2 = tc2 * 1000;
1031   }
1032   else {
1033     timeControl_2 = 0;
1034   }
1035   
1036   if( tc1 == 0 ) {
1037     return FALSE;
1038   }
1039   
1040   timeControl = tc1 * 1000;
1041   
1042   if (ti >= 0) {
1043     timeIncrement = ti * 1000;  /* convert to ms */
1044     movesPerSession = 0;
1045   } else {
1046     timeIncrement = 0;
1047     movesPerSession = mps;
1048   }
1049   return TRUE;
1050 }
1051
1052 void
1053 InitBackEnd2()
1054 {
1055     if (appData.debugMode) {
1056         fprintf(debugFP, "%s\n", programVersion);
1057     }
1058
1059     set_cont_sequence(appData.wrapContSeq);
1060     if (appData.matchGames > 0) {
1061         appData.matchMode = TRUE;
1062     } else if (appData.matchMode) {
1063         appData.matchGames = 1;
1064     }
1065     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1066         appData.matchGames = appData.sameColorGames;
1067     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1068         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1069         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1070     }
1071     Reset(TRUE, FALSE);
1072     if (appData.noChessProgram || first.protocolVersion == 1) {
1073       InitBackEnd3();
1074     } else {
1075       /* kludge: allow timeout for initial "feature" commands */
1076       FreezeUI();
1077       DisplayMessage("", _("Starting chess program"));
1078       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1079     }
1080 }
1081
1082 void
1083 InitBackEnd3 P((void))
1084 {
1085     GameMode initialMode;
1086     char buf[MSG_SIZ];
1087     int err;
1088
1089     InitChessProgram(&first, startedFromSetupPosition);
1090
1091
1092     if (appData.icsActive) {
1093 #ifdef WIN32
1094         /* [DM] Make a console window if needed [HGM] merged ifs */
1095         ConsoleCreate(); 
1096 #endif
1097         err = establish();
1098         if (err != 0) {
1099             if (*appData.icsCommPort != NULLCHAR) {
1100                 sprintf(buf, _("Could not open comm port %s"),  
1101                         appData.icsCommPort);
1102             } else {
1103                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1104                         appData.icsHost, appData.icsPort);
1105             }
1106             DisplayFatalError(buf, err, 1);
1107             return;
1108         }
1109         SetICSMode();
1110         telnetISR =
1111           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1112         fromUserISR =
1113           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1114     } else if (appData.noChessProgram) {
1115         SetNCPMode();
1116     } else {
1117         SetGNUMode();
1118     }
1119
1120     if (*appData.cmailGameName != NULLCHAR) {
1121         SetCmailMode();
1122         OpenLoopback(&cmailPR);
1123         cmailISR =
1124           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1125     }
1126     
1127     ThawUI();
1128     DisplayMessage("", "");
1129     if (StrCaseCmp(appData.initialMode, "") == 0) {
1130       initialMode = BeginningOfGame;
1131     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1132       initialMode = TwoMachinesPlay;
1133     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1134       initialMode = AnalyzeFile; 
1135     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1136       initialMode = AnalyzeMode;
1137     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1138       initialMode = MachinePlaysWhite;
1139     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1140       initialMode = MachinePlaysBlack;
1141     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1142       initialMode = EditGame;
1143     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1144       initialMode = EditPosition;
1145     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1146       initialMode = Training;
1147     } else {
1148       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1149       DisplayFatalError(buf, 0, 2);
1150       return;
1151     }
1152
1153     if (appData.matchMode) {
1154         /* Set up machine vs. machine match */
1155         if (appData.noChessProgram) {
1156             DisplayFatalError(_("Can't have a match with no chess programs"),
1157                               0, 2);
1158             return;
1159         }
1160         matchMode = TRUE;
1161         matchGame = 1;
1162         if (*appData.loadGameFile != NULLCHAR) {
1163             int index = appData.loadGameIndex; // [HGM] autoinc
1164             if(index<0) lastIndex = index = 1;
1165             if (!LoadGameFromFile(appData.loadGameFile,
1166                                   index,
1167                                   appData.loadGameFile, FALSE)) {
1168                 DisplayFatalError(_("Bad game file"), 0, 1);
1169                 return;
1170             }
1171         } else if (*appData.loadPositionFile != NULLCHAR) {
1172             int index = appData.loadPositionIndex; // [HGM] autoinc
1173             if(index<0) lastIndex = index = 1;
1174             if (!LoadPositionFromFile(appData.loadPositionFile,
1175                                       index,
1176                                       appData.loadPositionFile)) {
1177                 DisplayFatalError(_("Bad position file"), 0, 1);
1178                 return;
1179             }
1180         }
1181         TwoMachinesEvent();
1182     } else if (*appData.cmailGameName != NULLCHAR) {
1183         /* Set up cmail mode */
1184         ReloadCmailMsgEvent(TRUE);
1185     } else {
1186         /* Set up other modes */
1187         if (initialMode == AnalyzeFile) {
1188           if (*appData.loadGameFile == NULLCHAR) {
1189             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1190             return;
1191           }
1192         }
1193         if (*appData.loadGameFile != NULLCHAR) {
1194             (void) LoadGameFromFile(appData.loadGameFile,
1195                                     appData.loadGameIndex,
1196                                     appData.loadGameFile, TRUE);
1197         } else if (*appData.loadPositionFile != NULLCHAR) {
1198             (void) LoadPositionFromFile(appData.loadPositionFile,
1199                                         appData.loadPositionIndex,
1200                                         appData.loadPositionFile);
1201             /* [HGM] try to make self-starting even after FEN load */
1202             /* to allow automatic setup of fairy variants with wtm */
1203             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1204                 gameMode = BeginningOfGame;
1205                 setboardSpoiledMachineBlack = 1;
1206             }
1207             /* [HGM] loadPos: make that every new game uses the setup */
1208             /* from file as long as we do not switch variant          */
1209             if(!blackPlaysFirst) {
1210                 startedFromPositionFile = TRUE;
1211                 CopyBoard(filePosition, boards[0]);
1212             }
1213         }
1214         if (initialMode == AnalyzeMode) {
1215           if (appData.noChessProgram) {
1216             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1221             return;
1222           }
1223           AnalyzeModeEvent();
1224         } else if (initialMode == AnalyzeFile) {
1225           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1226           ShowThinkingEvent();
1227           AnalyzeFileEvent();
1228           AnalysisPeriodicEvent(1);
1229         } else if (initialMode == MachinePlaysWhite) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1232                               0, 2);
1233             return;
1234           }
1235           if (appData.icsActive) {
1236             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1237                               0, 2);
1238             return;
1239           }
1240           MachineWhiteEvent();
1241         } else if (initialMode == MachinePlaysBlack) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1244                               0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1249                               0, 2);
1250             return;
1251           }
1252           MachineBlackEvent();
1253         } else if (initialMode == TwoMachinesPlay) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           TwoMachinesEvent();
1265         } else if (initialMode == EditGame) {
1266           EditGameEvent();
1267         } else if (initialMode == EditPosition) {
1268           EditPositionEvent();
1269         } else if (initialMode == Training) {
1270           if (*appData.loadGameFile == NULLCHAR) {
1271             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1272             return;
1273           }
1274           TrainingEvent();
1275         }
1276     }
1277 }
1278
1279 /*
1280  * Establish will establish a contact to a remote host.port.
1281  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1282  *  used to talk to the host.
1283  * Returns 0 if okay, error code if not.
1284  */
1285 int
1286 establish()
1287 {
1288     char buf[MSG_SIZ];
1289
1290     if (*appData.icsCommPort != NULLCHAR) {
1291         /* Talk to the host through a serial comm port */
1292         return OpenCommPort(appData.icsCommPort, &icsPR);
1293
1294     } else if (*appData.gateway != NULLCHAR) {
1295         if (*appData.remoteShell == NULLCHAR) {
1296             /* Use the rcmd protocol to run telnet program on a gateway host */
1297             snprintf(buf, sizeof(buf), "%s %s %s",
1298                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1299             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300
1301         } else {
1302             /* Use the rsh program to run telnet program on a gateway host */
1303             if (*appData.remoteUser == NULLCHAR) {
1304                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1305                         appData.gateway, appData.telnetProgram,
1306                         appData.icsHost, appData.icsPort);
1307             } else {
1308                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1309                         appData.remoteShell, appData.gateway, 
1310                         appData.remoteUser, appData.telnetProgram,
1311                         appData.icsHost, appData.icsPort);
1312             }
1313             return StartChildProcess(buf, "", &icsPR);
1314
1315         }
1316     } else if (appData.useTelnet) {
1317         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318
1319     } else {
1320         /* TCP socket interface differs somewhat between
1321            Unix and NT; handle details in the front end.
1322            */
1323         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1324     }
1325 }
1326
1327 void
1328 show_bytes(fp, buf, count)
1329      FILE *fp;
1330      char *buf;
1331      int count;
1332 {
1333     while (count--) {
1334         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1335             fprintf(fp, "\\%03o", *buf & 0xff);
1336         } else {
1337             putc(*buf, fp);
1338         }
1339         buf++;
1340     }
1341     fflush(fp);
1342 }
1343
1344 /* Returns an errno value */
1345 int
1346 OutputMaybeTelnet(pr, message, count, outError)
1347      ProcRef pr;
1348      char *message;
1349      int count;
1350      int *outError;
1351 {
1352     char buf[8192], *p, *q, *buflim;
1353     int left, newcount, outcount;
1354
1355     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1356         *appData.gateway != NULLCHAR) {
1357         if (appData.debugMode) {
1358             fprintf(debugFP, ">ICS: ");
1359             show_bytes(debugFP, message, count);
1360             fprintf(debugFP, "\n");
1361         }
1362         return OutputToProcess(pr, message, count, outError);
1363     }
1364
1365     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1366     p = message;
1367     q = buf;
1368     left = count;
1369     newcount = 0;
1370     while (left) {
1371         if (q >= buflim) {
1372             if (appData.debugMode) {
1373                 fprintf(debugFP, ">ICS: ");
1374                 show_bytes(debugFP, buf, newcount);
1375                 fprintf(debugFP, "\n");
1376             }
1377             outcount = OutputToProcess(pr, buf, newcount, outError);
1378             if (outcount < newcount) return -1; /* to be sure */
1379             q = buf;
1380             newcount = 0;
1381         }
1382         if (*p == '\n') {
1383             *q++ = '\r';
1384             newcount++;
1385         } else if (((unsigned char) *p) == TN_IAC) {
1386             *q++ = (char) TN_IAC;
1387             newcount ++;
1388         }
1389         *q++ = *p++;
1390         newcount++;
1391         left--;
1392     }
1393     if (appData.debugMode) {
1394         fprintf(debugFP, ">ICS: ");
1395         show_bytes(debugFP, buf, newcount);
1396         fprintf(debugFP, "\n");
1397     }
1398     outcount = OutputToProcess(pr, buf, newcount, outError);
1399     if (outcount < newcount) return -1; /* to be sure */
1400     return count;
1401 }
1402
1403 void
1404 read_from_player(isr, closure, message, count, error)
1405      InputSourceRef isr;
1406      VOIDSTAR closure;
1407      char *message;
1408      int count;
1409      int error;
1410 {
1411     int outError, outCount;
1412     static int gotEof = 0;
1413
1414     /* Pass data read from player on to ICS */
1415     if (count > 0) {
1416         gotEof = 0;
1417         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1418         if (outCount < count) {
1419             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1420         }
1421     } else if (count < 0) {
1422         RemoveInputSource(isr);
1423         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1424     } else if (gotEof++ > 0) {
1425         RemoveInputSource(isr);
1426         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1427     }
1428 }
1429
1430 void
1431 KeepAlive()
1432 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1433     SendToICS("date\n");
1434     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435 }
1436
1437 /* added routine for printf style output to ics */
1438 void ics_printf(char *format, ...)
1439 {
1440     char buffer[MSG_SIZ];
1441     va_list args;
1442
1443     va_start(args, format);
1444     vsnprintf(buffer, sizeof(buffer), format, args);
1445     buffer[sizeof(buffer)-1] = '\0';
1446     SendToICS(buffer);
1447     va_end(args);
1448 }
1449
1450 void
1451 SendToICS(s)
1452      char *s;
1453 {
1454     int count, outCount, outError;
1455
1456     if (icsPR == NULL) return;
1457
1458     count = strlen(s);
1459     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1460     if (outCount < count) {
1461         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1462     }
1463 }
1464
1465 /* This is used for sending logon scripts to the ICS. Sending
1466    without a delay causes problems when using timestamp on ICC
1467    (at least on my machine). */
1468 void
1469 SendToICSDelayed(s,msdelay)
1470      char *s;
1471      long msdelay;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     if (appData.debugMode) {
1479         fprintf(debugFP, ">ICS: ");
1480         show_bytes(debugFP, s, count);
1481         fprintf(debugFP, "\n");
1482     }
1483     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1484                                       msdelay);
1485     if (outCount < count) {
1486         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1487     }
1488 }
1489
1490
1491 /* Remove all highlighting escape sequences in s
1492    Also deletes any suffix starting with '(' 
1493    */
1494 char *
1495 StripHighlightAndTitle(s)
1496      char *s;
1497 {
1498     static char retbuf[MSG_SIZ];
1499     char *p = retbuf;
1500
1501     while (*s != NULLCHAR) {
1502         while (*s == '\033') {
1503             while (*s != NULLCHAR && !isalpha(*s)) s++;
1504             if (*s != NULLCHAR) s++;
1505         }
1506         while (*s != NULLCHAR && *s != '\033') {
1507             if (*s == '(' || *s == '[') {
1508                 *p = NULLCHAR;
1509                 return retbuf;
1510             }
1511             *p++ = *s++;
1512         }
1513     }
1514     *p = NULLCHAR;
1515     return retbuf;
1516 }
1517
1518 /* Remove all highlighting escape sequences in s */
1519 char *
1520 StripHighlight(s)
1521      char *s;
1522 {
1523     static char retbuf[MSG_SIZ];
1524     char *p = retbuf;
1525
1526     while (*s != NULLCHAR) {
1527         while (*s == '\033') {
1528             while (*s != NULLCHAR && !isalpha(*s)) s++;
1529             if (*s != NULLCHAR) s++;
1530         }
1531         while (*s != NULLCHAR && *s != '\033') {
1532             *p++ = *s++;
1533         }
1534     }
1535     *p = NULLCHAR;
1536     return retbuf;
1537 }
1538
1539 char *variantNames[] = VARIANT_NAMES;
1540 char *
1541 VariantName(v)
1542      VariantClass v;
1543 {
1544     return variantNames[v];
1545 }
1546
1547
1548 /* Identify a variant from the strings the chess servers use or the
1549    PGN Variant tag names we use. */
1550 VariantClass
1551 StringToVariant(e)
1552      char *e;
1553 {
1554     char *p;
1555     int wnum = -1;
1556     VariantClass v = VariantNormal;
1557     int i, found = FALSE;
1558     char buf[MSG_SIZ];
1559
1560     if (!e) return v;
1561
1562     /* [HGM] skip over optional board-size prefixes */
1563     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1564         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1565         while( *e++ != '_');
1566     }
1567
1568     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1569         v = VariantNormal;
1570         found = TRUE;
1571     } else
1572     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573       if (StrCaseStr(e, variantNames[i])) {
1574         v = (VariantClass) i;
1575         found = TRUE;
1576         break;
1577       }
1578     }
1579
1580     if (!found) {
1581       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582           || StrCaseStr(e, "wild/fr") 
1583           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584         v = VariantFischeRandom;
1585       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586                  (i = 1, p = StrCaseStr(e, "w"))) {
1587         p += i;
1588         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1589         if (isdigit(*p)) {
1590           wnum = atoi(p);
1591         } else {
1592           wnum = -1;
1593         }
1594         switch (wnum) {
1595         case 0: /* FICS only, actually */
1596         case 1:
1597           /* Castling legal even if K starts on d-file */
1598           v = VariantWildCastle;
1599           break;
1600         case 2:
1601         case 3:
1602         case 4:
1603           /* Castling illegal even if K & R happen to start in
1604              normal positions. */
1605           v = VariantNoCastle;
1606           break;
1607         case 5:
1608         case 7:
1609         case 8:
1610         case 10:
1611         case 11:
1612         case 12:
1613         case 13:
1614         case 14:
1615         case 15:
1616         case 18:
1617         case 19:
1618           /* Castling legal iff K & R start in normal positions */
1619           v = VariantNormal;
1620           break;
1621         case 6:
1622         case 20:
1623         case 21:
1624           /* Special wilds for position setup; unclear what to do here */
1625           v = VariantLoadable;
1626           break;
1627         case 9:
1628           /* Bizarre ICC game */
1629           v = VariantTwoKings;
1630           break;
1631         case 16:
1632           v = VariantKriegspiel;
1633           break;
1634         case 17:
1635           v = VariantLosers;
1636           break;
1637         case 22:
1638           v = VariantFischeRandom;
1639           break;
1640         case 23:
1641           v = VariantCrazyhouse;
1642           break;
1643         case 24:
1644           v = VariantBughouse;
1645           break;
1646         case 25:
1647           v = Variant3Check;
1648           break;
1649         case 26:
1650           /* Not quite the same as FICS suicide! */
1651           v = VariantGiveaway;
1652           break;
1653         case 27:
1654           v = VariantAtomic;
1655           break;
1656         case 28:
1657           v = VariantShatranj;
1658           break;
1659
1660         /* Temporary names for future ICC types.  The name *will* change in 
1661            the next xboard/WinBoard release after ICC defines it. */
1662         case 29:
1663           v = Variant29;
1664           break;
1665         case 30:
1666           v = Variant30;
1667           break;
1668         case 31:
1669           v = Variant31;
1670           break;
1671         case 32:
1672           v = Variant32;
1673           break;
1674         case 33:
1675           v = Variant33;
1676           break;
1677         case 34:
1678           v = Variant34;
1679           break;
1680         case 35:
1681           v = Variant35;
1682           break;
1683         case 36:
1684           v = Variant36;
1685           break;
1686         case 37:
1687           v = VariantShogi;
1688           break;
1689         case 38:
1690           v = VariantXiangqi;
1691           break;
1692         case 39:
1693           v = VariantCourier;
1694           break;
1695         case 40:
1696           v = VariantGothic;
1697           break;
1698         case 41:
1699           v = VariantCapablanca;
1700           break;
1701         case 42:
1702           v = VariantKnightmate;
1703           break;
1704         case 43:
1705           v = VariantFairy;
1706           break;
1707         case 44:
1708           v = VariantCylinder;
1709           break;
1710         case 45:
1711           v = VariantFalcon;
1712           break;
1713         case 46:
1714           v = VariantCapaRandom;
1715           break;
1716         case 47:
1717           v = VariantBerolina;
1718           break;
1719         case 48:
1720           v = VariantJanus;
1721           break;
1722         case 49:
1723           v = VariantSuper;
1724           break;
1725         case 50:
1726           v = VariantGreat;
1727           break;
1728         case -1:
1729           /* Found "wild" or "w" in the string but no number;
1730              must assume it's normal chess. */
1731           v = VariantNormal;
1732           break;
1733         default:
1734           sprintf(buf, _("Unknown wild type %d"), wnum);
1735           DisplayError(buf, 0);
1736           v = VariantUnknown;
1737           break;
1738         }
1739       }
1740     }
1741     if (appData.debugMode) {
1742       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743               e, wnum, VariantName(v));
1744     }
1745     return v;
1746 }
1747
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1750
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752    advance *index beyond it, and set leftover_start to the new value of
1753    *index; else return FALSE.  If pattern contains the character '*', it
1754    matches any sequence of characters not containing '\r', '\n', or the
1755    character following the '*' (if any), and the matched sequence(s) are
1756    copied into star_match.
1757    */
1758 int
1759 looking_at(buf, index, pattern)
1760      char *buf;
1761      int *index;
1762      char *pattern;
1763 {
1764     char *bufp = &buf[*index], *patternp = pattern;
1765     int star_count = 0;
1766     char *matchp = star_match[0];
1767     
1768     for (;;) {
1769         if (*patternp == NULLCHAR) {
1770             *index = leftover_start = bufp - buf;
1771             *matchp = NULLCHAR;
1772             return TRUE;
1773         }
1774         if (*bufp == NULLCHAR) return FALSE;
1775         if (*patternp == '*') {
1776             if (*bufp == *(patternp + 1)) {
1777                 *matchp = NULLCHAR;
1778                 matchp = star_match[++star_count];
1779                 patternp += 2;
1780                 bufp++;
1781                 continue;
1782             } else if (*bufp == '\n' || *bufp == '\r') {
1783                 patternp++;
1784                 if (*patternp == NULLCHAR)
1785                   continue;
1786                 else
1787                   return FALSE;
1788             } else {
1789                 *matchp++ = *bufp++;
1790                 continue;
1791             }
1792         }
1793         if (*patternp != *bufp) return FALSE;
1794         patternp++;
1795         bufp++;
1796     }
1797 }
1798
1799 void
1800 SendToPlayer(data, length)
1801      char *data;
1802      int length;
1803 {
1804     int error, outCount;
1805     outCount = OutputToProcess(NoProc, data, length, &error);
1806     if (outCount < length) {
1807         DisplayFatalError(_("Error writing to display"), error, 1);
1808     }
1809 }
1810
1811 void
1812 PackHolding(packed, holding)
1813      char packed[];
1814      char *holding;
1815 {
1816     char *p = holding;
1817     char *q = packed;
1818     int runlength = 0;
1819     int curr = 9999;
1820     do {
1821         if (*p == curr) {
1822             runlength++;
1823         } else {
1824             switch (runlength) {
1825               case 0:
1826                 break;
1827               case 1:
1828                 *q++ = curr;
1829                 break;
1830               case 2:
1831                 *q++ = curr;
1832                 *q++ = curr;
1833                 break;
1834               default:
1835                 sprintf(q, "%d", runlength);
1836                 while (*q) q++;
1837                 *q++ = curr;
1838                 break;
1839             }
1840             runlength = 1;
1841             curr = *p;
1842         }
1843     } while (*p++);
1844     *q = NULLCHAR;
1845 }
1846
1847 /* Telnet protocol requests from the front end */
1848 void
1849 TelnetRequest(ddww, option)
1850      unsigned char ddww, option;
1851 {
1852     unsigned char msg[3];
1853     int outCount, outError;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1856
1857     if (appData.debugMode) {
1858         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1859         switch (ddww) {
1860           case TN_DO:
1861             ddwwStr = "DO";
1862             break;
1863           case TN_DONT:
1864             ddwwStr = "DONT";
1865             break;
1866           case TN_WILL:
1867             ddwwStr = "WILL";
1868             break;
1869           case TN_WONT:
1870             ddwwStr = "WONT";
1871             break;
1872           default:
1873             ddwwStr = buf1;
1874             sprintf(buf1, "%d", ddww);
1875             break;
1876         }
1877         switch (option) {
1878           case TN_ECHO:
1879             optionStr = "ECHO";
1880             break;
1881           default:
1882             optionStr = buf2;
1883             sprintf(buf2, "%d", option);
1884             break;
1885         }
1886         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1887     }
1888     msg[0] = TN_IAC;
1889     msg[1] = ddww;
1890     msg[2] = option;
1891     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1892     if (outCount < 3) {
1893         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894     }
1895 }
1896
1897 void
1898 DoEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DO, TN_ECHO);
1902 }
1903
1904 void
1905 DontEcho()
1906 {
1907     if (!appData.icsActive) return;
1908     TelnetRequest(TN_DONT, TN_ECHO);
1909 }
1910
1911 void
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1913 {
1914     /* put the holdings sent to us by the server on the board holdings area */
1915     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916     char p;
1917     ChessSquare piece;
1918
1919     if(gameInfo.holdingsWidth < 2)  return;
1920     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1921         return; // prevent overwriting by pre-board holdings
1922
1923     if( (int)lowestPiece >= BlackPawn ) {
1924         holdingsColumn = 0;
1925         countsColumn = 1;
1926         holdingsStartRow = BOARD_HEIGHT-1;
1927         direction = -1;
1928     } else {
1929         holdingsColumn = BOARD_WIDTH-1;
1930         countsColumn = BOARD_WIDTH-2;
1931         holdingsStartRow = 0;
1932         direction = 1;
1933     }
1934
1935     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1936         board[i][holdingsColumn] = EmptySquare;
1937         board[i][countsColumn]   = (ChessSquare) 0;
1938     }
1939     while( (p=*holdings++) != NULLCHAR ) {
1940         piece = CharToPiece( ToUpper(p) );
1941         if(piece == EmptySquare) continue;
1942         /*j = (int) piece - (int) WhitePawn;*/
1943         j = PieceToNumber(piece);
1944         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1945         if(j < 0) continue;               /* should not happen */
1946         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1947         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1948         board[holdingsStartRow+j*direction][countsColumn]++;
1949     }
1950 }
1951
1952
1953 void
1954 VariantSwitch(Board board, VariantClass newVariant)
1955 {
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957    Board oldBoard;
1958
1959    startedFromPositionFile = FALSE;
1960    if(gameInfo.variant == newVariant) return;
1961
1962    /* [HGM] This routine is called each time an assignment is made to
1963     * gameInfo.variant during a game, to make sure the board sizes
1964     * are set to match the new variant. If that means adding or deleting
1965     * holdings, we shift the playing board accordingly
1966     * This kludge is needed because in ICS observe mode, we get boards
1967     * of an ongoing game without knowing the variant, and learn about the
1968     * latter only later. This can be because of the move list we requested,
1969     * in which case the game history is refilled from the beginning anyway,
1970     * but also when receiving holdings of a crazyhouse game. In the latter
1971     * case we want to add those holdings to the already received position.
1972     */
1973
1974    
1975    if (appData.debugMode) {
1976      fprintf(debugFP, "Switch board from %s to %s\n",
1977              VariantName(gameInfo.variant), VariantName(newVariant));
1978      setbuf(debugFP, NULL);
1979    }
1980    shuffleOpenings = 0;       /* [HGM] shuffle */
1981    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982    switch(newVariant) 
1983      {
1984      case VariantShogi:
1985        newWidth = 9;  newHeight = 9;
1986        gameInfo.holdingsSize = 7;
1987      case VariantBughouse:
1988      case VariantCrazyhouse:
1989        newHoldingsWidth = 2; break;
1990      case VariantGreat:
1991        newWidth = 10;
1992      case VariantSuper:
1993        newHoldingsWidth = 2;
1994        gameInfo.holdingsSize = 8;
1995        break;
1996      case VariantGothic:
1997      case VariantCapablanca:
1998      case VariantCapaRandom:
1999        newWidth = 10;
2000      default:
2001        newHoldingsWidth = gameInfo.holdingsSize = 0;
2002      };
2003    
2004    if(newWidth  != gameInfo.boardWidth  ||
2005       newHeight != gameInfo.boardHeight ||
2006       newHoldingsWidth != gameInfo.holdingsWidth ) {
2007      
2008      /* shift position to new playing area, if needed */
2009      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010        for(i=0; i<BOARD_HEIGHT; i++) 
2011          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013              board[i][j];
2014        for(i=0; i<newHeight; i++) {
2015          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017        }
2018      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019        for(i=0; i<BOARD_HEIGHT; i++)
2020          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022              board[i][j];
2023      }
2024      gameInfo.boardWidth  = newWidth;
2025      gameInfo.boardHeight = newHeight;
2026      gameInfo.holdingsWidth = newHoldingsWidth;
2027      gameInfo.variant = newVariant;
2028      InitDrawingSizes(-2, 0);
2029    } else gameInfo.variant = newVariant;
2030    CopyBoard(oldBoard, board);   // remember correctly formatted board
2031      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2032    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2033 }
2034
2035 static int loggedOn = FALSE;
2036
2037 /*-- Game start info cache: --*/
2038 int gs_gamenum;
2039 char gs_kind[MSG_SIZ];
2040 static char player1Name[128] = "";
2041 static char player2Name[128] = "";
2042 static char cont_seq[] = "\n\\   ";
2043 static int player1Rating = -1;
2044 static int player2Rating = -1;
2045 /*----------------------------*/
2046
2047 ColorClass curColor = ColorNormal;
2048 int suppressKibitz = 0;
2049
2050 void
2051 read_from_ics(isr, closure, data, count, error)
2052      InputSourceRef isr;
2053      VOIDSTAR closure;
2054      char *data;
2055      int count;
2056      int error;
2057 {
2058 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2059 #define STARTED_NONE 0
2060 #define STARTED_MOVES 1
2061 #define STARTED_BOARD 2
2062 #define STARTED_OBSERVE 3
2063 #define STARTED_HOLDINGS 4
2064 #define STARTED_CHATTER 5
2065 #define STARTED_COMMENT 6
2066 #define STARTED_MOVES_NOHIDE 7
2067     
2068     static int started = STARTED_NONE;
2069     static char parse[20000];
2070     static int parse_pos = 0;
2071     static char buf[BUF_SIZE + 1];
2072     static int firstTime = TRUE, intfSet = FALSE;
2073     static ColorClass prevColor = ColorNormal;
2074     static int savingComment = FALSE;
2075     static int cmatch = 0; // continuation sequence match
2076     char *bp;
2077     char str[500];
2078     int i, oldi;
2079     int buf_len;
2080     int next_out;
2081     int tkind;
2082     int backup;    /* [DM] For zippy color lines */
2083     char *p;
2084     char talker[MSG_SIZ]; // [HGM] chat
2085     int channel;
2086
2087     if (appData.debugMode) {
2088       if (!error) {
2089         fprintf(debugFP, "<ICS: ");
2090         show_bytes(debugFP, data, count);
2091         fprintf(debugFP, "\n");
2092       }
2093     }
2094
2095     if (appData.debugMode) { int f = forwardMostMove;
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2098                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2099     }
2100     if (count > 0) {
2101         /* If last read ended with a partial line that we couldn't parse,
2102            prepend it to the new read and try again. */
2103         if (leftover_len > 0) {
2104             for (i=0; i<leftover_len; i++)
2105               buf[i] = buf[leftover_start + i];
2106         }
2107
2108     /* copy new characters into the buffer */
2109     bp = buf + leftover_len;
2110     buf_len=leftover_len;
2111     for (i=0; i<count; i++)
2112     {
2113         // ignore these
2114         if (data[i] == '\r')
2115             continue;
2116
2117         // join lines split by ICS?
2118         if (!appData.noJoin)
2119         {
2120             /*
2121                 Joining just consists of finding matches against the
2122                 continuation sequence, and discarding that sequence
2123                 if found instead of copying it.  So, until a match
2124                 fails, there's nothing to do since it might be the
2125                 complete sequence, and thus, something we don't want
2126                 copied.
2127             */
2128             if (data[i] == cont_seq[cmatch])
2129             {
2130                 cmatch++;
2131                 if (cmatch == strlen(cont_seq))
2132                 {
2133                     cmatch = 0; // complete match.  just reset the counter
2134
2135                     /*
2136                         it's possible for the ICS to not include the space
2137                         at the end of the last word, making our [correct]
2138                         join operation fuse two separate words.  the server
2139                         does this when the space occurs at the width setting.
2140                     */
2141                     if (!buf_len || buf[buf_len-1] != ' ')
2142                     {
2143                         *bp++ = ' ';
2144                         buf_len++;
2145                     }
2146                 }
2147                 continue;
2148             }
2149             else if (cmatch)
2150             {
2151                 /*
2152                     match failed, so we have to copy what matched before
2153                     falling through and copying this character.  In reality,
2154                     this will only ever be just the newline character, but
2155                     it doesn't hurt to be precise.
2156                 */
2157                 strncpy(bp, cont_seq, cmatch);
2158                 bp += cmatch;
2159                 buf_len += cmatch;
2160                 cmatch = 0;
2161             }
2162         }
2163
2164         // copy this char
2165         *bp++ = data[i];
2166         buf_len++;
2167     }
2168
2169         buf[buf_len] = NULLCHAR;
2170         next_out = leftover_len;
2171         leftover_start = 0;
2172         
2173         i = 0;
2174         while (i < buf_len) {
2175             /* Deal with part of the TELNET option negotiation
2176                protocol.  We refuse to do anything beyond the
2177                defaults, except that we allow the WILL ECHO option,
2178                which ICS uses to turn off password echoing when we are
2179                directly connected to it.  We reject this option
2180                if localLineEditing mode is on (always on in xboard)
2181                and we are talking to port 23, which might be a real
2182                telnet server that will try to keep WILL ECHO on permanently.
2183              */
2184             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2185                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2186                 unsigned char option;
2187                 oldi = i;
2188                 switch ((unsigned char) buf[++i]) {
2189                   case TN_WILL:
2190                     if (appData.debugMode)
2191                       fprintf(debugFP, "\n<WILL ");
2192                     switch (option = (unsigned char) buf[++i]) {
2193                       case TN_ECHO:
2194                         if (appData.debugMode)
2195                           fprintf(debugFP, "ECHO ");
2196                         /* Reply only if this is a change, according
2197                            to the protocol rules. */
2198                         if (remoteEchoOption) break;
2199                         if (appData.localLineEditing &&
2200                             atoi(appData.icsPort) == TN_PORT) {
2201                             TelnetRequest(TN_DONT, TN_ECHO);
2202                         } else {
2203                             EchoOff();
2204                             TelnetRequest(TN_DO, TN_ECHO);
2205                             remoteEchoOption = TRUE;
2206                         }
2207                         break;
2208                       default:
2209                         if (appData.debugMode)
2210                           fprintf(debugFP, "%d ", option);
2211                         /* Whatever this is, we don't want it. */
2212                         TelnetRequest(TN_DONT, option);
2213                         break;
2214                     }
2215                     break;
2216                   case TN_WONT:
2217                     if (appData.debugMode)
2218                       fprintf(debugFP, "\n<WONT ");
2219                     switch (option = (unsigned char) buf[++i]) {
2220                       case TN_ECHO:
2221                         if (appData.debugMode)
2222                           fprintf(debugFP, "ECHO ");
2223                         /* Reply only if this is a change, according
2224                            to the protocol rules. */
2225                         if (!remoteEchoOption) break;
2226                         EchoOn();
2227                         TelnetRequest(TN_DONT, TN_ECHO);
2228                         remoteEchoOption = FALSE;
2229                         break;
2230                       default:
2231                         if (appData.debugMode)
2232                           fprintf(debugFP, "%d ", (unsigned char) option);
2233                         /* Whatever this is, it must already be turned
2234                            off, because we never agree to turn on
2235                            anything non-default, so according to the
2236                            protocol rules, we don't reply. */
2237                         break;
2238                     }
2239                     break;
2240                   case TN_DO:
2241                     if (appData.debugMode)
2242                       fprintf(debugFP, "\n<DO ");
2243                     switch (option = (unsigned char) buf[++i]) {
2244                       default:
2245                         /* Whatever this is, we refuse to do it. */
2246                         if (appData.debugMode)
2247                           fprintf(debugFP, "%d ", option);
2248                         TelnetRequest(TN_WONT, option);
2249                         break;
2250                     }
2251                     break;
2252                   case TN_DONT:
2253                     if (appData.debugMode)
2254                       fprintf(debugFP, "\n<DONT ");
2255                     switch (option = (unsigned char) buf[++i]) {
2256                       default:
2257                         if (appData.debugMode)
2258                           fprintf(debugFP, "%d ", option);
2259                         /* Whatever this is, we are already not doing
2260                            it, because we never agree to do anything
2261                            non-default, so according to the protocol
2262                            rules, we don't reply. */
2263                         break;
2264                     }
2265                     break;
2266                   case TN_IAC:
2267                     if (appData.debugMode)
2268                       fprintf(debugFP, "\n<IAC ");
2269                     /* Doubled IAC; pass it through */
2270                     i--;
2271                     break;
2272                   default:
2273                     if (appData.debugMode)
2274                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2275                     /* Drop all other telnet commands on the floor */
2276                     break;
2277                 }
2278                 if (oldi > next_out)
2279                   SendToPlayer(&buf[next_out], oldi - next_out);
2280                 if (++i > next_out)
2281                   next_out = i;
2282                 continue;
2283             }
2284                 
2285             /* OK, this at least will *usually* work */
2286             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2287                 loggedOn = TRUE;
2288             }
2289             
2290             if (loggedOn && !intfSet) {
2291                 if (ics_type == ICS_ICC) {
2292                   sprintf(str,
2293                           "/set-quietly interface %s\n/set-quietly style 12\n",
2294                           programVersion);
2295                 } else if (ics_type == ICS_CHESSNET) {
2296                   sprintf(str, "/style 12\n");
2297                 } else {
2298                   strcpy(str, "alias $ @\n$set interface ");
2299                   strcat(str, programVersion);
2300                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2301 #ifdef WIN32
2302                   strcat(str, "$iset nohighlight 1\n");
2303 #endif
2304                   strcat(str, "$iset lock 1\n$style 12\n");
2305                 }
2306                 SendToICS(str);
2307                 NotifyFrontendLogin();
2308                 intfSet = TRUE;
2309             }
2310
2311             if (started == STARTED_COMMENT) {
2312                 /* Accumulate characters in comment */
2313                 parse[parse_pos++] = buf[i];
2314                 if (buf[i] == '\n') {
2315                     parse[parse_pos] = NULLCHAR;
2316                     if(chattingPartner>=0) {
2317                         char mess[MSG_SIZ];
2318                         sprintf(mess, "%s%s", talker, parse);
2319                         OutputChatMessage(chattingPartner, mess);
2320                         chattingPartner = -1;
2321                     } else
2322                     if(!suppressKibitz) // [HGM] kibitz
2323                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2324                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2325                         int nrDigit = 0, nrAlph = 0, i;
2326                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2327                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2328                         parse[parse_pos] = NULLCHAR;
2329                         // try to be smart: if it does not look like search info, it should go to
2330                         // ICS interaction window after all, not to engine-output window.
2331                         for(i=0; i<parse_pos; i++) { // count letters and digits
2332                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2333                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2334                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2335                         }
2336                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2337                             int depth=0; float score;
2338                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2339                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2340                                 pvInfoList[forwardMostMove-1].depth = depth;
2341                                 pvInfoList[forwardMostMove-1].score = 100*score;
2342                             }
2343                             OutputKibitz(suppressKibitz, parse);
2344                         } else {
2345                             char tmp[MSG_SIZ];
2346                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2347                             SendToPlayer(tmp, strlen(tmp));
2348                         }
2349                     }
2350                     started = STARTED_NONE;
2351                 } else {
2352                     /* Don't match patterns against characters in chatter */
2353                     i++;
2354                     continue;
2355                 }
2356             }
2357             if (started == STARTED_CHATTER) {
2358                 if (buf[i] != '\n') {
2359                     /* Don't match patterns against characters in chatter */
2360                     i++;
2361                     continue;
2362                 }
2363                 started = STARTED_NONE;
2364             }
2365
2366             /* Kludge to deal with rcmd protocol */
2367             if (firstTime && looking_at(buf, &i, "\001*")) {
2368                 DisplayFatalError(&buf[1], 0, 1);
2369                 continue;
2370             } else {
2371                 firstTime = FALSE;
2372             }
2373
2374             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2375                 ics_type = ICS_ICC;
2376                 ics_prefix = "/";
2377                 if (appData.debugMode)
2378                   fprintf(debugFP, "ics_type %d\n", ics_type);
2379                 continue;
2380             }
2381             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2382                 ics_type = ICS_FICS;
2383                 ics_prefix = "$";
2384                 if (appData.debugMode)
2385                   fprintf(debugFP, "ics_type %d\n", ics_type);
2386                 continue;
2387             }
2388             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2389                 ics_type = ICS_CHESSNET;
2390                 ics_prefix = "/";
2391                 if (appData.debugMode)
2392                   fprintf(debugFP, "ics_type %d\n", ics_type);
2393                 continue;
2394             }
2395
2396             if (!loggedOn &&
2397                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2398                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2399                  looking_at(buf, &i, "will be \"*\""))) {
2400               strcpy(ics_handle, star_match[0]);
2401               continue;
2402             }
2403
2404             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2405               char buf[MSG_SIZ];
2406               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2407               DisplayIcsInteractionTitle(buf);
2408               have_set_title = TRUE;
2409             }
2410
2411             /* skip finger notes */
2412             if (started == STARTED_NONE &&
2413                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2414                  (buf[i] == '1' && buf[i+1] == '0')) &&
2415                 buf[i+2] == ':' && buf[i+3] == ' ') {
2416               started = STARTED_CHATTER;
2417               i += 3;
2418               continue;
2419             }
2420
2421             /* skip formula vars */
2422             if (started == STARTED_NONE &&
2423                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2424               started = STARTED_CHATTER;
2425               i += 3;
2426               continue;
2427             }
2428
2429             oldi = i;
2430             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2431             if (appData.autoKibitz && started == STARTED_NONE && 
2432                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2433                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2434                 if(looking_at(buf, &i, "* kibitzes: ") &&
2435                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2436                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2437                         suppressKibitz = TRUE;
2438                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2439                                 && (gameMode == IcsPlayingWhite)) ||
2440                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2441                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2442                             started = STARTED_CHATTER; // own kibitz we simply discard
2443                         else {
2444                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2445                             parse_pos = 0; parse[0] = NULLCHAR;
2446                             savingComment = TRUE;
2447                             suppressKibitz = gameMode != IcsObserving ? 2 :
2448                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2449                         } 
2450                         continue;
2451                 } else
2452                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2453                     started = STARTED_CHATTER;
2454                     suppressKibitz = TRUE;
2455                 }
2456             } // [HGM] kibitz: end of patch
2457
2458 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2459
2460             // [HGM] chat: intercept tells by users for which we have an open chat window
2461             channel = -1;
2462             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2463                                            looking_at(buf, &i, "* whispers:") ||
2464                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2465                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2466                 int p;
2467                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2468                 chattingPartner = -1;
2469
2470                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2471                 for(p=0; p<MAX_CHAT; p++) {
2472                     if(channel == atoi(chatPartner[p])) {
2473                     talker[0] = '['; strcat(talker, "]");
2474                     chattingPartner = p; break;
2475                     }
2476                 } else
2477                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2478                 for(p=0; p<MAX_CHAT; p++) {
2479                     if(!strcmp("WHISPER", chatPartner[p])) {
2480                         talker[0] = '['; strcat(talker, "]");
2481                         chattingPartner = p; break;
2482                     }
2483                 }
2484                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2485                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2486                     talker[0] = 0;
2487                     chattingPartner = p; break;
2488                 }
2489                 if(chattingPartner<0) i = oldi; else {
2490                     started = STARTED_COMMENT;
2491                     parse_pos = 0; parse[0] = NULLCHAR;
2492                     savingComment = TRUE;
2493                     suppressKibitz = TRUE;
2494                 }
2495             } // [HGM] chat: end of patch
2496
2497             if (appData.zippyTalk || appData.zippyPlay) {
2498                 /* [DM] Backup address for color zippy lines */
2499                 backup = i;
2500 #if ZIPPY
2501        #ifdef WIN32
2502                if (loggedOn == TRUE)
2503                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2504                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2505        #else
2506                 if (ZippyControl(buf, &i) ||
2507                     ZippyConverse(buf, &i) ||
2508                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2509                       loggedOn = TRUE;
2510                       if (!appData.colorize) continue;
2511                 }
2512        #endif
2513 #endif
2514             } // [DM] 'else { ' deleted
2515                 if (
2516                     /* Regular tells and says */
2517                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2518                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2519                     looking_at(buf, &i, "* says: ") ||
2520                     /* Don't color "message" or "messages" output */
2521                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2522                     looking_at(buf, &i, "*. * at *:*: ") ||
2523                     looking_at(buf, &i, "--* (*:*): ") ||
2524                     /* Message notifications (same color as tells) */
2525                     looking_at(buf, &i, "* has left a message ") ||
2526                     looking_at(buf, &i, "* just sent you a message:\n") ||
2527                     /* Whispers and kibitzes */
2528                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2529                     looking_at(buf, &i, "* kibitzes: ") ||
2530                     /* Channel tells */
2531                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2532
2533                   if (tkind == 1 && strchr(star_match[0], ':')) {
2534                       /* Avoid "tells you:" spoofs in channels */
2535                      tkind = 3;
2536                   }
2537                   if (star_match[0][0] == NULLCHAR ||
2538                       strchr(star_match[0], ' ') ||
2539                       (tkind == 3 && strchr(star_match[1], ' '))) {
2540                     /* Reject bogus matches */
2541                     i = oldi;
2542                   } else {
2543                     if (appData.colorize) {
2544                       if (oldi > next_out) {
2545                         SendToPlayer(&buf[next_out], oldi - next_out);
2546                         next_out = oldi;
2547                       }
2548                       switch (tkind) {
2549                       case 1:
2550                         Colorize(ColorTell, FALSE);
2551                         curColor = ColorTell;
2552                         break;
2553                       case 2:
2554                         Colorize(ColorKibitz, FALSE);
2555                         curColor = ColorKibitz;
2556                         break;
2557                       case 3:
2558                         p = strrchr(star_match[1], '(');
2559                         if (p == NULL) {
2560                           p = star_match[1];
2561                         } else {
2562                           p++;
2563                         }
2564                         if (atoi(p) == 1) {
2565                           Colorize(ColorChannel1, FALSE);
2566                           curColor = ColorChannel1;
2567                         } else {
2568                           Colorize(ColorChannel, FALSE);
2569                           curColor = ColorChannel;
2570                         }
2571                         break;
2572                       case 5:
2573                         curColor = ColorNormal;
2574                         break;
2575                       }
2576                     }
2577                     if (started == STARTED_NONE && appData.autoComment &&
2578                         (gameMode == IcsObserving ||
2579                          gameMode == IcsPlayingWhite ||
2580                          gameMode == IcsPlayingBlack)) {
2581                       parse_pos = i - oldi;
2582                       memcpy(parse, &buf[oldi], parse_pos);
2583                       parse[parse_pos] = NULLCHAR;
2584                       started = STARTED_COMMENT;
2585                       savingComment = TRUE;
2586                     } else {
2587                       started = STARTED_CHATTER;
2588                       savingComment = FALSE;
2589                     }
2590                     loggedOn = TRUE;
2591                     continue;
2592                   }
2593                 }
2594
2595                 if (looking_at(buf, &i, "* s-shouts: ") ||
2596                     looking_at(buf, &i, "* c-shouts: ")) {
2597                     if (appData.colorize) {
2598                         if (oldi > next_out) {
2599                             SendToPlayer(&buf[next_out], oldi - next_out);
2600                             next_out = oldi;
2601                         }
2602                         Colorize(ColorSShout, FALSE);
2603                         curColor = ColorSShout;
2604                     }
2605                     loggedOn = TRUE;
2606                     started = STARTED_CHATTER;
2607                     continue;
2608                 }
2609
2610                 if (looking_at(buf, &i, "--->")) {
2611                     loggedOn = TRUE;
2612                     continue;
2613                 }
2614
2615                 if (looking_at(buf, &i, "* shouts: ") ||
2616                     looking_at(buf, &i, "--> ")) {
2617                     if (appData.colorize) {
2618                         if (oldi > next_out) {
2619                             SendToPlayer(&buf[next_out], oldi - next_out);
2620                             next_out = oldi;
2621                         }
2622                         Colorize(ColorShout, FALSE);
2623                         curColor = ColorShout;
2624                     }
2625                     loggedOn = TRUE;
2626                     started = STARTED_CHATTER;
2627                     continue;
2628                 }
2629
2630                 if (looking_at( buf, &i, "Challenge:")) {
2631                     if (appData.colorize) {
2632                         if (oldi > next_out) {
2633                             SendToPlayer(&buf[next_out], oldi - next_out);
2634                             next_out = oldi;
2635                         }
2636                         Colorize(ColorChallenge, FALSE);
2637                         curColor = ColorChallenge;
2638                     }
2639                     loggedOn = TRUE;
2640                     continue;
2641                 }
2642
2643                 if (looking_at(buf, &i, "* offers you") ||
2644                     looking_at(buf, &i, "* offers to be") ||
2645                     looking_at(buf, &i, "* would like to") ||
2646                     looking_at(buf, &i, "* requests to") ||
2647                     looking_at(buf, &i, "Your opponent offers") ||
2648                     looking_at(buf, &i, "Your opponent requests")) {
2649
2650                     if (appData.colorize) {
2651                         if (oldi > next_out) {
2652                             SendToPlayer(&buf[next_out], oldi - next_out);
2653                             next_out = oldi;
2654                         }
2655                         Colorize(ColorRequest, FALSE);
2656                         curColor = ColorRequest;
2657                     }
2658                     continue;
2659                 }
2660
2661                 if (looking_at(buf, &i, "* (*) seeking")) {
2662                     if (appData.colorize) {
2663                         if (oldi > next_out) {
2664                             SendToPlayer(&buf[next_out], oldi - next_out);
2665                             next_out = oldi;
2666                         }
2667                         Colorize(ColorSeek, FALSE);
2668                         curColor = ColorSeek;
2669                     }
2670                     continue;
2671             }
2672
2673             if (looking_at(buf, &i, "\\   ")) {
2674                 if (prevColor != ColorNormal) {
2675                     if (oldi > next_out) {
2676                         SendToPlayer(&buf[next_out], oldi - next_out);
2677                         next_out = oldi;
2678                     }
2679                     Colorize(prevColor, TRUE);
2680                     curColor = prevColor;
2681                 }
2682                 if (savingComment) {
2683                     parse_pos = i - oldi;
2684                     memcpy(parse, &buf[oldi], parse_pos);
2685                     parse[parse_pos] = NULLCHAR;
2686                     started = STARTED_COMMENT;
2687                 } else {
2688                     started = STARTED_CHATTER;
2689                 }
2690                 continue;
2691             }
2692
2693             if (looking_at(buf, &i, "Black Strength :") ||
2694                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2695                 looking_at(buf, &i, "<10>") ||
2696                 looking_at(buf, &i, "#@#")) {
2697                 /* Wrong board style */
2698                 loggedOn = TRUE;
2699                 SendToICS(ics_prefix);
2700                 SendToICS("set style 12\n");
2701                 SendToICS(ics_prefix);
2702                 SendToICS("refresh\n");
2703                 continue;
2704             }
2705             
2706             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2707                 ICSInitScript();
2708                 have_sent_ICS_logon = 1;
2709                 continue;
2710             }
2711               
2712             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2713                 (looking_at(buf, &i, "\n<12> ") ||
2714                  looking_at(buf, &i, "<12> "))) {
2715                 loggedOn = TRUE;
2716                 if (oldi > next_out) {
2717                     SendToPlayer(&buf[next_out], oldi - next_out);
2718                 }
2719                 next_out = i;
2720                 started = STARTED_BOARD;
2721                 parse_pos = 0;
2722                 continue;
2723             }
2724
2725             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2726                 looking_at(buf, &i, "<b1> ")) {
2727                 if (oldi > next_out) {
2728                     SendToPlayer(&buf[next_out], oldi - next_out);
2729                 }
2730                 next_out = i;
2731                 started = STARTED_HOLDINGS;
2732                 parse_pos = 0;
2733                 continue;
2734             }
2735
2736             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2737                 loggedOn = TRUE;
2738                 /* Header for a move list -- first line */
2739
2740                 switch (ics_getting_history) {
2741                   case H_FALSE:
2742                     switch (gameMode) {
2743                       case IcsIdle:
2744                       case BeginningOfGame:
2745                         /* User typed "moves" or "oldmoves" while we
2746                            were idle.  Pretend we asked for these
2747                            moves and soak them up so user can step
2748                            through them and/or save them.
2749                            */
2750                         Reset(FALSE, TRUE);
2751                         gameMode = IcsObserving;
2752                         ModeHighlight();
2753                         ics_gamenum = -1;
2754                         ics_getting_history = H_GOT_UNREQ_HEADER;
2755                         break;
2756                       case EditGame: /*?*/
2757                       case EditPosition: /*?*/
2758                         /* Should above feature work in these modes too? */
2759                         /* For now it doesn't */
2760                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2761                         break;
2762                       default:
2763                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2764                         break;
2765                     }
2766                     break;
2767                   case H_REQUESTED:
2768                     /* Is this the right one? */
2769                     if (gameInfo.white && gameInfo.black &&
2770                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2771                         strcmp(gameInfo.black, star_match[2]) == 0) {
2772                         /* All is well */
2773                         ics_getting_history = H_GOT_REQ_HEADER;
2774                     }
2775                     break;
2776                   case H_GOT_REQ_HEADER:
2777                   case H_GOT_UNREQ_HEADER:
2778                   case H_GOT_UNWANTED_HEADER:
2779                   case H_GETTING_MOVES:
2780                     /* Should not happen */
2781                     DisplayError(_("Error gathering move list: two headers"), 0);
2782                     ics_getting_history = H_FALSE;
2783                     break;
2784                 }
2785
2786                 /* Save player ratings into gameInfo if needed */
2787                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2788                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2789                     (gameInfo.whiteRating == -1 ||
2790                      gameInfo.blackRating == -1)) {
2791
2792                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2793                     gameInfo.blackRating = string_to_rating(star_match[3]);
2794                     if (appData.debugMode)
2795                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2796                               gameInfo.whiteRating, gameInfo.blackRating);
2797                 }
2798                 continue;
2799             }
2800
2801             if (looking_at(buf, &i,
2802               "* * match, initial time: * minute*, increment: * second")) {
2803                 /* Header for a move list -- second line */
2804                 /* Initial board will follow if this is a wild game */
2805                 if (gameInfo.event != NULL) free(gameInfo.event);
2806                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2807                 gameInfo.event = StrSave(str);
2808                 /* [HGM] we switched variant. Translate boards if needed. */
2809                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2810                 continue;
2811             }
2812
2813             if (looking_at(buf, &i, "Move  ")) {
2814                 /* Beginning of a move list */
2815                 switch (ics_getting_history) {
2816                   case H_FALSE:
2817                     /* Normally should not happen */
2818                     /* Maybe user hit reset while we were parsing */
2819                     break;
2820                   case H_REQUESTED:
2821                     /* Happens if we are ignoring a move list that is not
2822                      * the one we just requested.  Common if the user
2823                      * tries to observe two games without turning off
2824                      * getMoveList */
2825                     break;
2826                   case H_GETTING_MOVES:
2827                     /* Should not happen */
2828                     DisplayError(_("Error gathering move list: nested"), 0);
2829                     ics_getting_history = H_FALSE;
2830                     break;
2831                   case H_GOT_REQ_HEADER:
2832                     ics_getting_history = H_GETTING_MOVES;
2833                     started = STARTED_MOVES;
2834                     parse_pos = 0;
2835                     if (oldi > next_out) {
2836                         SendToPlayer(&buf[next_out], oldi - next_out);
2837                     }
2838                     break;
2839                   case H_GOT_UNREQ_HEADER:
2840                     ics_getting_history = H_GETTING_MOVES;
2841                     started = STARTED_MOVES_NOHIDE;
2842                     parse_pos = 0;
2843                     break;
2844                   case H_GOT_UNWANTED_HEADER:
2845                     ics_getting_history = H_FALSE;
2846                     break;
2847                 }
2848                 continue;
2849             }                           
2850             
2851             if (looking_at(buf, &i, "% ") ||
2852                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2853                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2854                 savingComment = FALSE;
2855                 switch (started) {
2856                   case STARTED_MOVES:
2857                   case STARTED_MOVES_NOHIDE:
2858                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2859                     parse[parse_pos + i - oldi] = NULLCHAR;
2860                     ParseGameHistory(parse);
2861 #if ZIPPY
2862                     if (appData.zippyPlay && first.initDone) {
2863                         FeedMovesToProgram(&first, forwardMostMove);
2864                         if (gameMode == IcsPlayingWhite) {
2865                             if (WhiteOnMove(forwardMostMove)) {
2866                                 if (first.sendTime) {
2867                                   if (first.useColors) {
2868                                     SendToProgram("black\n", &first); 
2869                                   }
2870                                   SendTimeRemaining(&first, TRUE);
2871                                 }
2872                                 if (first.useColors) {
2873                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2874                                 }
2875                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2876                                 first.maybeThinking = TRUE;
2877                             } else {
2878                                 if (first.usePlayother) {
2879                                   if (first.sendTime) {
2880                                     SendTimeRemaining(&first, TRUE);
2881                                   }
2882                                   SendToProgram("playother\n", &first);
2883                                   firstMove = FALSE;
2884                                 } else {
2885                                   firstMove = TRUE;
2886                                 }
2887                             }
2888                         } else if (gameMode == IcsPlayingBlack) {
2889                             if (!WhiteOnMove(forwardMostMove)) {
2890                                 if (first.sendTime) {
2891                                   if (first.useColors) {
2892                                     SendToProgram("white\n", &first);
2893                                   }
2894                                   SendTimeRemaining(&first, FALSE);
2895                                 }
2896                                 if (first.useColors) {
2897                                   SendToProgram("black\n", &first);
2898                                 }
2899                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2900                                 first.maybeThinking = TRUE;
2901                             } else {
2902                                 if (first.usePlayother) {
2903                                   if (first.sendTime) {
2904                                     SendTimeRemaining(&first, FALSE);
2905                                   }
2906                                   SendToProgram("playother\n", &first);
2907                                   firstMove = FALSE;
2908                                 } else {
2909                                   firstMove = TRUE;
2910                                 }
2911                             }
2912                         }                       
2913                     }
2914 #endif
2915                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2916                         /* Moves came from oldmoves or moves command
2917                            while we weren't doing anything else.
2918                            */
2919                         currentMove = forwardMostMove;
2920                         ClearHighlights();/*!!could figure this out*/
2921                         flipView = appData.flipView;
2922                         DrawPosition(TRUE, boards[currentMove]);
2923                         DisplayBothClocks();
2924                         sprintf(str, "%s vs. %s",
2925                                 gameInfo.white, gameInfo.black);
2926                         DisplayTitle(str);
2927                         gameMode = IcsIdle;
2928                     } else {
2929                         /* Moves were history of an active game */
2930                         if (gameInfo.resultDetails != NULL) {
2931                             free(gameInfo.resultDetails);
2932                             gameInfo.resultDetails = NULL;
2933                         }
2934                     }
2935                     HistorySet(parseList, backwardMostMove,
2936                                forwardMostMove, currentMove-1);
2937                     DisplayMove(currentMove - 1);
2938                     if (started == STARTED_MOVES) next_out = i;
2939                     started = STARTED_NONE;
2940                     ics_getting_history = H_FALSE;
2941                     break;
2942
2943                   case STARTED_OBSERVE:
2944                     started = STARTED_NONE;
2945                     SendToICS(ics_prefix);
2946                     SendToICS("refresh\n");
2947                     break;
2948
2949                   default:
2950                     break;
2951                 }
2952                 if(bookHit) { // [HGM] book: simulate book reply
2953                     static char bookMove[MSG_SIZ]; // a bit generous?
2954
2955                     programStats.nodes = programStats.depth = programStats.time = 
2956                     programStats.score = programStats.got_only_move = 0;
2957                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2958
2959                     strcpy(bookMove, "move ");
2960                     strcat(bookMove, bookHit);
2961                     HandleMachineMove(bookMove, &first);
2962                 }
2963                 continue;
2964             }
2965             
2966             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2967                  started == STARTED_HOLDINGS ||
2968                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2969                 /* Accumulate characters in move list or board */
2970                 parse[parse_pos++] = buf[i];
2971             }
2972             
2973             /* Start of game messages.  Mostly we detect start of game
2974                when the first board image arrives.  On some versions
2975                of the ICS, though, we need to do a "refresh" after starting
2976                to observe in order to get the current board right away. */
2977             if (looking_at(buf, &i, "Adding game * to observation list")) {
2978                 started = STARTED_OBSERVE;
2979                 continue;
2980             }
2981
2982             /* Handle auto-observe */
2983             if (appData.autoObserve &&
2984                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2985                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2986                 char *player;
2987                 /* Choose the player that was highlighted, if any. */
2988                 if (star_match[0][0] == '\033' ||
2989                     star_match[1][0] != '\033') {
2990                     player = star_match[0];
2991                 } else {
2992                     player = star_match[2];
2993                 }
2994                 sprintf(str, "%sobserve %s\n",
2995                         ics_prefix, StripHighlightAndTitle(player));
2996                 SendToICS(str);
2997
2998                 /* Save ratings from notify string */
2999                 strcpy(player1Name, star_match[0]);
3000                 player1Rating = string_to_rating(star_match[1]);
3001                 strcpy(player2Name, star_match[2]);
3002                 player2Rating = string_to_rating(star_match[3]);
3003
3004                 if (appData.debugMode)
3005                   fprintf(debugFP, 
3006                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3007                           player1Name, player1Rating,
3008                           player2Name, player2Rating);
3009
3010                 continue;
3011             }
3012
3013             /* Deal with automatic examine mode after a game,
3014                and with IcsObserving -> IcsExamining transition */
3015             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3016                 looking_at(buf, &i, "has made you an examiner of game *")) {
3017
3018                 int gamenum = atoi(star_match[0]);
3019                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3020                     gamenum == ics_gamenum) {
3021                     /* We were already playing or observing this game;
3022                        no need to refetch history */
3023                     gameMode = IcsExamining;
3024                     if (pausing) {
3025                         pauseExamForwardMostMove = forwardMostMove;
3026                     } else if (currentMove < forwardMostMove) {
3027                         ForwardInner(forwardMostMove);
3028                     }
3029                 } else {
3030                     /* I don't think this case really can happen */
3031                     SendToICS(ics_prefix);
3032                     SendToICS("refresh\n");
3033                 }
3034                 continue;
3035             }    
3036             
3037             /* Error messages */
3038 //          if (ics_user_moved) {
3039             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3040                 if (looking_at(buf, &i, "Illegal move") ||
3041                     looking_at(buf, &i, "Not a legal move") ||
3042                     looking_at(buf, &i, "Your king is in check") ||
3043                     looking_at(buf, &i, "It isn't your turn") ||
3044                     looking_at(buf, &i, "It is not your move")) {
3045                     /* Illegal move */
3046                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3047                         currentMove = --forwardMostMove;
3048                         DisplayMove(currentMove - 1); /* before DMError */
3049                         DrawPosition(FALSE, boards[currentMove]);
3050                         SwitchClocks();
3051                         DisplayBothClocks();
3052                     }
3053                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3054                     ics_user_moved = 0;
3055                     continue;
3056                 }
3057             }
3058
3059             if (looking_at(buf, &i, "still have time") ||
3060                 looking_at(buf, &i, "not out of time") ||
3061                 looking_at(buf, &i, "either player is out of time") ||
3062                 looking_at(buf, &i, "has timeseal; checking")) {
3063                 /* We must have called his flag a little too soon */
3064                 whiteFlag = blackFlag = FALSE;
3065                 continue;
3066             }
3067
3068             if (looking_at(buf, &i, "added * seconds to") ||
3069                 looking_at(buf, &i, "seconds were added to")) {
3070                 /* Update the clocks */
3071                 SendToICS(ics_prefix);
3072                 SendToICS("refresh\n");
3073                 continue;
3074             }
3075
3076             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3077                 ics_clock_paused = TRUE;
3078                 StopClocks();
3079                 continue;
3080             }
3081
3082             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3083                 ics_clock_paused = FALSE;
3084                 StartClocks();
3085                 continue;
3086             }
3087
3088             /* Grab player ratings from the Creating: message.
3089                Note we have to check for the special case when
3090                the ICS inserts things like [white] or [black]. */
3091             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3092                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3093                 /* star_matches:
3094                    0    player 1 name (not necessarily white)
3095                    1    player 1 rating
3096                    2    empty, white, or black (IGNORED)
3097                    3    player 2 name (not necessarily black)
3098                    4    player 2 rating
3099                    
3100                    The names/ratings are sorted out when the game
3101                    actually starts (below).
3102                 */
3103                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3104                 player1Rating = string_to_rating(star_match[1]);
3105                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3106                 player2Rating = string_to_rating(star_match[4]);
3107
3108                 if (appData.debugMode)
3109                   fprintf(debugFP, 
3110                           "Ratings from 'Creating:' %s %d, %s %d\n",
3111                           player1Name, player1Rating,
3112                           player2Name, player2Rating);
3113
3114                 continue;
3115             }
3116             
3117             /* Improved generic start/end-of-game messages */
3118             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3119                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3120                 /* If tkind == 0: */
3121                 /* star_match[0] is the game number */
3122                 /*           [1] is the white player's name */
3123                 /*           [2] is the black player's name */
3124                 /* For end-of-game: */
3125                 /*           [3] is the reason for the game end */
3126                 /*           [4] is a PGN end game-token, preceded by " " */
3127                 /* For start-of-game: */
3128                 /*           [3] begins with "Creating" or "Continuing" */
3129                 /*           [4] is " *" or empty (don't care). */
3130                 int gamenum = atoi(star_match[0]);
3131                 char *whitename, *blackname, *why, *endtoken;
3132                 ChessMove endtype = (ChessMove) 0;
3133
3134                 if (tkind == 0) {
3135                   whitename = star_match[1];
3136                   blackname = star_match[2];
3137                   why = star_match[3];
3138                   endtoken = star_match[4];
3139                 } else {
3140                   whitename = star_match[1];
3141                   blackname = star_match[3];
3142                   why = star_match[5];
3143                   endtoken = star_match[6];
3144                 }
3145
3146                 /* Game start messages */
3147                 if (strncmp(why, "Creating ", 9) == 0 ||
3148                     strncmp(why, "Continuing ", 11) == 0) {
3149                     gs_gamenum = gamenum;
3150                     strcpy(gs_kind, strchr(why, ' ') + 1);
3151 #if ZIPPY
3152                     if (appData.zippyPlay) {
3153                         ZippyGameStart(whitename, blackname);
3154                     }
3155 #endif /*ZIPPY*/
3156                     continue;
3157                 }
3158
3159                 /* Game end messages */
3160                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3161                     ics_gamenum != gamenum) {
3162                     continue;
3163                 }
3164                 while (endtoken[0] == ' ') endtoken++;
3165                 switch (endtoken[0]) {
3166                   case '*':
3167                   default:
3168                     endtype = GameUnfinished;
3169                     break;
3170                   case '0':
3171                     endtype = BlackWins;
3172                     break;
3173                   case '1':
3174                     if (endtoken[1] == '/')
3175                       endtype = GameIsDrawn;
3176                     else
3177                       endtype = WhiteWins;
3178                     break;
3179                 }
3180                 GameEnds(endtype, why, GE_ICS);
3181 #if ZIPPY
3182                 if (appData.zippyPlay && first.initDone) {
3183                     ZippyGameEnd(endtype, why);
3184                     if (first.pr == NULL) {
3185                       /* Start the next process early so that we'll
3186                          be ready for the next challenge */
3187                       StartChessProgram(&first);
3188                     }
3189                     /* Send "new" early, in case this command takes
3190                        a long time to finish, so that we'll be ready
3191                        for the next challenge. */
3192                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3193                     Reset(TRUE, TRUE);
3194                 }
3195 #endif /*ZIPPY*/
3196                 continue;
3197             }
3198
3199             if (looking_at(buf, &i, "Removing game * from observation") ||
3200                 looking_at(buf, &i, "no longer observing game *") ||
3201                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3202                 if (gameMode == IcsObserving &&
3203                     atoi(star_match[0]) == ics_gamenum)
3204                   {
3205                       /* icsEngineAnalyze */
3206                       if (appData.icsEngineAnalyze) {
3207                             ExitAnalyzeMode();
3208                             ModeHighlight();
3209                       }
3210                       StopClocks();
3211                       gameMode = IcsIdle;
3212                       ics_gamenum = -1;
3213                       ics_user_moved = FALSE;
3214                   }
3215                 continue;
3216             }
3217
3218             if (looking_at(buf, &i, "no longer examining game *")) {
3219                 if (gameMode == IcsExamining &&
3220                     atoi(star_match[0]) == ics_gamenum)
3221                   {
3222                       gameMode = IcsIdle;
3223                       ics_gamenum = -1;
3224                       ics_user_moved = FALSE;
3225                   }
3226                 continue;
3227             }
3228
3229             /* Advance leftover_start past any newlines we find,
3230                so only partial lines can get reparsed */
3231             if (looking_at(buf, &i, "\n")) {
3232                 prevColor = curColor;
3233                 if (curColor != ColorNormal) {
3234                     if (oldi > next_out) {
3235                         SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = oldi;
3237                     }
3238                     Colorize(ColorNormal, FALSE);
3239                     curColor = ColorNormal;
3240                 }
3241                 if (started == STARTED_BOARD) {
3242                     started = STARTED_NONE;
3243                     parse[parse_pos] = NULLCHAR;
3244                     ParseBoard12(parse);
3245                     ics_user_moved = 0;
3246
3247                     /* Send premove here */
3248                     if (appData.premove) {
3249                       char str[MSG_SIZ];
3250                       if (currentMove == 0 &&
3251                           gameMode == IcsPlayingWhite &&
3252                           appData.premoveWhite) {
3253                         sprintf(str, "%s\n", appData.premoveWhiteText);
3254                         if (appData.debugMode)
3255                           fprintf(debugFP, "Sending premove:\n");
3256                         SendToICS(str);
3257                       } else if (currentMove == 1 &&
3258                                  gameMode == IcsPlayingBlack &&
3259                                  appData.premoveBlack) {
3260                         sprintf(str, "%s\n", appData.premoveBlackText);
3261                         if (appData.debugMode)
3262                           fprintf(debugFP, "Sending premove:\n");
3263                         SendToICS(str);
3264                       } else if (gotPremove) {
3265                         gotPremove = 0;
3266                         ClearPremoveHighlights();
3267                         if (appData.debugMode)
3268                           fprintf(debugFP, "Sending premove:\n");
3269                           UserMoveEvent(premoveFromX, premoveFromY, 
3270                                         premoveToX, premoveToY, 
3271                                         premovePromoChar);
3272                       }
3273                     }
3274
3275                     /* Usually suppress following prompt */
3276                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3277                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3278                         if (looking_at(buf, &i, "*% ")) {
3279                             savingComment = FALSE;
3280                         }
3281                     }
3282                     next_out = i;
3283                 } else if (started == STARTED_HOLDINGS) {
3284                     int gamenum;
3285                     char new_piece[MSG_SIZ];
3286                     started = STARTED_NONE;
3287                     parse[parse_pos] = NULLCHAR;
3288                     if (appData.debugMode)
3289                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3290                                                         parse, currentMove);
3291                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3292                         gamenum == ics_gamenum) {
3293                         if (gameInfo.variant == VariantNormal) {
3294                           /* [HGM] We seem to switch variant during a game!
3295                            * Presumably no holdings were displayed, so we have
3296                            * to move the position two files to the right to
3297                            * create room for them!
3298                            */
3299                           VariantClass newVariant;
3300                           switch(gameInfo.boardWidth) { // base guess on board width
3301                                 case 9:  newVariant = VariantShogi; break;
3302                                 case 10: newVariant = VariantGreat; break;
3303                                 default: newVariant = VariantCrazyhouse; break;
3304                           }
3305                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3306                           /* Get a move list just to see the header, which
3307                              will tell us whether this is really bug or zh */
3308                           if (ics_getting_history == H_FALSE) {
3309                             ics_getting_history = H_REQUESTED;
3310                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3311                             SendToICS(str);
3312                           }
3313                         }
3314                         new_piece[0] = NULLCHAR;
3315                         sscanf(parse, "game %d white [%s black [%s <- %s",
3316                                &gamenum, white_holding, black_holding,
3317                                new_piece);
3318                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3319                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3320                         /* [HGM] copy holdings to board holdings area */
3321                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3322                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3323                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3324 #if ZIPPY
3325                         if (appData.zippyPlay && first.initDone) {
3326                             ZippyHoldings(white_holding, black_holding,
3327                                           new_piece);
3328                         }
3329 #endif /*ZIPPY*/
3330                         if (tinyLayout || smallLayout) {
3331                             char wh[16], bh[16];
3332                             PackHolding(wh, white_holding);
3333                             PackHolding(bh, black_holding);
3334                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3335                                     gameInfo.white, gameInfo.black);
3336                         } else {
3337                             sprintf(str, "%s [%s] vs. %s [%s]",
3338                                     gameInfo.white, white_holding,
3339                                     gameInfo.black, black_holding);
3340                         }
3341
3342                         DrawPosition(FALSE, boards[currentMove]);
3343                         DisplayTitle(str);
3344                     }
3345                     /* Suppress following prompt */
3346                     if (looking_at(buf, &i, "*% ")) {
3347                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3348                         savingComment = FALSE;
3349                     }
3350                     next_out = i;
3351                 }
3352                 continue;
3353             }
3354
3355             i++;                /* skip unparsed character and loop back */
3356         }
3357         
3358         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3359             started != STARTED_HOLDINGS && i > next_out) {
3360             SendToPlayer(&buf[next_out], i - next_out);
3361             next_out = i;
3362         }
3363         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3364         
3365         leftover_len = buf_len - leftover_start;
3366         /* if buffer ends with something we couldn't parse,
3367            reparse it after appending the next read */
3368         
3369     } else if (count == 0) {
3370         RemoveInputSource(isr);
3371         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3372     } else {
3373         DisplayFatalError(_("Error reading from ICS"), error, 1);
3374     }
3375 }
3376
3377
3378 /* Board style 12 looks like this:
3379    
3380    <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
3381    
3382  * The "<12> " is stripped before it gets to this routine.  The two
3383  * trailing 0's (flip state and clock ticking) are later addition, and
3384  * some chess servers may not have them, or may have only the first.
3385  * Additional trailing fields may be added in the future.  
3386  */
3387
3388 #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"
3389
3390 #define RELATION_OBSERVING_PLAYED    0
3391 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3392 #define RELATION_PLAYING_MYMOVE      1
3393 #define RELATION_PLAYING_NOTMYMOVE  -1
3394 #define RELATION_EXAMINING           2
3395 #define RELATION_ISOLATED_BOARD     -3
3396 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3397
3398 void
3399 ParseBoard12(string)
3400      char *string;
3401
3402     GameMode newGameMode;
3403     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3404     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3405     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3406     char to_play, board_chars[200];
3407     char move_str[500], str[500], elapsed_time[500];
3408     char black[32], white[32];
3409     Board board;
3410     int prevMove = currentMove;
3411     int ticking = 2;
3412     ChessMove moveType;
3413     int fromX, fromY, toX, toY;
3414     char promoChar;
3415     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3416     char *bookHit = NULL; // [HGM] book
3417     Boolean weird = FALSE, reqFlag = FALSE;
3418
3419     fromX = fromY = toX = toY = -1;
3420     
3421     newGame = FALSE;
3422
3423     if (appData.debugMode)
3424       fprintf(debugFP, _("Parsing board: %s\n"), string);
3425
3426     move_str[0] = NULLCHAR;
3427     elapsed_time[0] = NULLCHAR;
3428     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3429         int  i = 0, j;
3430         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3431             if(string[i] == ' ') { ranks++; files = 0; }
3432             else files++;
3433             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3434             i++;
3435         }
3436         for(j = 0; j <i; j++) board_chars[j] = string[j];
3437         board_chars[i] = '\0';
3438         string += i + 1;
3439     }
3440     n = sscanf(string, PATTERN, &to_play, &double_push,
3441                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3442                &gamenum, white, black, &relation, &basetime, &increment,
3443                &white_stren, &black_stren, &white_time, &black_time,
3444                &moveNum, str, elapsed_time, move_str, &ics_flip,
3445                &ticking);
3446
3447     if (n < 21) {
3448         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3449         DisplayError(str, 0);
3450         return;
3451     }
3452
3453     /* Convert the move number to internal form */
3454     moveNum = (moveNum - 1) * 2;
3455     if (to_play == 'B') moveNum++;
3456     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3457       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3458                         0, 1);
3459       return;
3460     }
3461     
3462     switch (relation) {
3463       case RELATION_OBSERVING_PLAYED:
3464       case RELATION_OBSERVING_STATIC:
3465         if (gamenum == -1) {
3466             /* Old ICC buglet */
3467             relation = RELATION_OBSERVING_STATIC;
3468         }
3469         newGameMode = IcsObserving;
3470         break;
3471       case RELATION_PLAYING_MYMOVE:
3472       case RELATION_PLAYING_NOTMYMOVE:
3473         newGameMode =
3474           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3475             IcsPlayingWhite : IcsPlayingBlack;
3476         break;
3477       case RELATION_EXAMINING:
3478         newGameMode = IcsExamining;
3479         break;
3480       case RELATION_ISOLATED_BOARD:
3481       default:
3482         /* Just display this board.  If user was doing something else,
3483            we will forget about it until the next board comes. */ 
3484         newGameMode = IcsIdle;
3485         break;
3486       case RELATION_STARTING_POSITION:
3487         newGameMode = gameMode;
3488         break;
3489     }
3490     
3491     /* Modify behavior for initial board display on move listing
3492        of wild games.
3493        */
3494     switch (ics_getting_history) {
3495       case H_FALSE:
3496       case H_REQUESTED:
3497         break;
3498       case H_GOT_REQ_HEADER:
3499       case H_GOT_UNREQ_HEADER:
3500         /* This is the initial position of the current game */
3501         gamenum = ics_gamenum;
3502         moveNum = 0;            /* old ICS bug workaround */
3503         if (to_play == 'B') {
3504           startedFromSetupPosition = TRUE;
3505           blackPlaysFirst = TRUE;
3506           moveNum = 1;
3507           if (forwardMostMove == 0) forwardMostMove = 1;
3508           if (backwardMostMove == 0) backwardMostMove = 1;
3509           if (currentMove == 0) currentMove = 1;
3510         }
3511         newGameMode = gameMode;
3512         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3513         break;
3514       case H_GOT_UNWANTED_HEADER:
3515         /* This is an initial board that we don't want */
3516         return;
3517       case H_GETTING_MOVES:
3518         /* Should not happen */
3519         DisplayError(_("Error gathering move list: extra board"), 0);
3520         ics_getting_history = H_FALSE;
3521         return;
3522     }
3523
3524    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3525                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3526      /* [HGM] We seem to have switched variant unexpectedly
3527       * Try to guess new variant from board size
3528       */
3529           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3530           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3531           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3532           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3533           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3534           if(!weird) newVariant = VariantNormal;
3535           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3536           /* Get a move list just to see the header, which
3537              will tell us whether this is really bug or zh */
3538           if (ics_getting_history == H_FALSE) {
3539             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3540             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3541             SendToICS(str);
3542           }
3543     }
3544     
3545     /* Take action if this is the first board of a new game, or of a
3546        different game than is currently being displayed.  */
3547     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3548         relation == RELATION_ISOLATED_BOARD) {
3549         
3550         /* Forget the old game and get the history (if any) of the new one */
3551         if (gameMode != BeginningOfGame) {
3552           Reset(TRUE, TRUE);
3553         }
3554         newGame = TRUE;
3555         if (appData.autoRaiseBoard) BoardToTop();
3556         prevMove = -3;
3557         if (gamenum == -1) {
3558             newGameMode = IcsIdle;
3559         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3560                    appData.getMoveList && !reqFlag) {
3561             /* Need to get game history */
3562             ics_getting_history = H_REQUESTED;
3563             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3564             SendToICS(str);
3565         }
3566         
3567         /* Initially flip the board to have black on the bottom if playing
3568            black or if the ICS flip flag is set, but let the user change
3569            it with the Flip View button. */
3570         flipView = appData.autoFlipView ? 
3571           (newGameMode == IcsPlayingBlack) || ics_flip :
3572           appData.flipView;
3573         
3574         /* Done with values from previous mode; copy in new ones */
3575         gameMode = newGameMode;
3576         ModeHighlight();
3577         ics_gamenum = gamenum;
3578         if (gamenum == gs_gamenum) {
3579             int klen = strlen(gs_kind);
3580             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3581             sprintf(str, "ICS %s", gs_kind);
3582             gameInfo.event = StrSave(str);
3583         } else {
3584             gameInfo.event = StrSave("ICS game");
3585         }
3586         gameInfo.site = StrSave(appData.icsHost);
3587         gameInfo.date = PGNDate();
3588         gameInfo.round = StrSave("-");
3589         gameInfo.white = StrSave(white);
3590         gameInfo.black = StrSave(black);
3591         timeControl = basetime * 60 * 1000;
3592         timeControl_2 = 0;
3593         timeIncrement = increment * 1000;
3594         movesPerSession = 0;
3595         gameInfo.timeControl = TimeControlTagValue();
3596         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3597   if (appData.debugMode) {
3598     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3599     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3600     setbuf(debugFP, NULL);
3601   }
3602
3603         gameInfo.outOfBook = NULL;
3604         
3605         /* Do we have the ratings? */
3606         if (strcmp(player1Name, white) == 0 &&
3607             strcmp(player2Name, black) == 0) {
3608             if (appData.debugMode)
3609               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3610                       player1Rating, player2Rating);
3611             gameInfo.whiteRating = player1Rating;
3612             gameInfo.blackRating = player2Rating;
3613         } else if (strcmp(player2Name, white) == 0 &&
3614                    strcmp(player1Name, black) == 0) {
3615             if (appData.debugMode)
3616               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3617                       player2Rating, player1Rating);
3618             gameInfo.whiteRating = player2Rating;
3619             gameInfo.blackRating = player1Rating;
3620         }
3621         player1Name[0] = player2Name[0] = NULLCHAR;
3622
3623         /* Silence shouts if requested */
3624         if (appData.quietPlay &&
3625             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3626             SendToICS(ics_prefix);
3627             SendToICS("set shout 0\n");
3628         }
3629     }
3630     
3631     /* Deal with midgame name changes */
3632     if (!newGame) {
3633         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3634             if (gameInfo.white) free(gameInfo.white);
3635             gameInfo.white = StrSave(white);
3636         }
3637         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3638             if (gameInfo.black) free(gameInfo.black);
3639             gameInfo.black = StrSave(black);
3640         }
3641     }
3642     
3643     /* Throw away game result if anything actually changes in examine mode */
3644     if (gameMode == IcsExamining && !newGame) {
3645         gameInfo.result = GameUnfinished;
3646         if (gameInfo.resultDetails != NULL) {
3647             free(gameInfo.resultDetails);
3648             gameInfo.resultDetails = NULL;
3649         }
3650     }
3651     
3652     /* In pausing && IcsExamining mode, we ignore boards coming
3653        in if they are in a different variation than we are. */
3654     if (pauseExamInvalid) return;
3655     if (pausing && gameMode == IcsExamining) {
3656         if (moveNum <= pauseExamForwardMostMove) {
3657             pauseExamInvalid = TRUE;
3658             forwardMostMove = pauseExamForwardMostMove;
3659             return;
3660         }
3661     }
3662     
3663   if (appData.debugMode) {
3664     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3665   }
3666     /* Parse the board */
3667     for (k = 0; k < ranks; k++) {
3668       for (j = 0; j < files; j++)
3669         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3670       if(gameInfo.holdingsWidth > 1) {
3671            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3672            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3673       }
3674     }
3675     CopyBoard(boards[moveNum], board);
3676     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3677     if (moveNum == 0) {
3678         startedFromSetupPosition =
3679           !CompareBoards(board, initialPosition);
3680         if(startedFromSetupPosition)
3681             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3682     }
3683
3684     /* [HGM] Set castling rights. Take the outermost Rooks,
3685        to make it also work for FRC opening positions. Note that board12
3686        is really defective for later FRC positions, as it has no way to
3687        indicate which Rook can castle if they are on the same side of King.
3688        For the initial position we grant rights to the outermost Rooks,
3689        and remember thos rights, and we then copy them on positions
3690        later in an FRC game. This means WB might not recognize castlings with
3691        Rooks that have moved back to their original position as illegal,
3692        but in ICS mode that is not its job anyway.
3693     */
3694     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3695     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3696
3697         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3698             if(board[0][i] == WhiteRook) j = i;
3699         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3700         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3701             if(board[0][i] == WhiteRook) j = i;
3702         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3703         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3704             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3706         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3707             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3708         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3709
3710         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3713         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3714             if(board[BOARD_HEIGHT-1][k] == bKing)
3715                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3716     } else { int r;
3717         r = boards[moveNum][CASTLING][0] = initialRights[0];
3718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3719         r = boards[moveNum][CASTLING][1] = initialRights[1];
3720         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3721         r = boards[moveNum][CASTLING][3] = initialRights[3];
3722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3723         r = boards[moveNum][CASTLING][4] = initialRights[4];
3724         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3725         /* wildcastle kludge: always assume King has rights */
3726         r = boards[moveNum][CASTLING][2] = initialRights[2];
3727         r = boards[moveNum][CASTLING][5] = initialRights[5];
3728     }
3729     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3730     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3731
3732     
3733     if (ics_getting_history == H_GOT_REQ_HEADER ||
3734         ics_getting_history == H_GOT_UNREQ_HEADER) {
3735         /* This was an initial position from a move list, not
3736            the current position */
3737         return;
3738     }
3739     
3740     /* Update currentMove and known move number limits */
3741     newMove = newGame || moveNum > forwardMostMove;
3742
3743     if (newGame) {
3744         forwardMostMove = backwardMostMove = currentMove = moveNum;
3745         if (gameMode == IcsExamining && moveNum == 0) {
3746           /* Workaround for ICS limitation: we are not told the wild
3747              type when starting to examine a game.  But if we ask for
3748              the move list, the move list header will tell us */
3749             ics_getting_history = H_REQUESTED;
3750             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3751             SendToICS(str);
3752         }
3753     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3754                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3755 #if ZIPPY
3756         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3757         /* [HGM] applied this also to an engine that is silently watching        */
3758         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3759             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3760             gameInfo.variant == currentlyInitializedVariant) {
3761           takeback = forwardMostMove - moveNum;
3762           for (i = 0; i < takeback; i++) {
3763             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3764             SendToProgram("undo\n", &first);
3765           }
3766         }
3767 #endif
3768
3769         forwardMostMove = moveNum;
3770         if (!pausing || currentMove > forwardMostMove)
3771           currentMove = forwardMostMove;
3772     } else {
3773         /* New part of history that is not contiguous with old part */ 
3774         if (pausing && gameMode == IcsExamining) {
3775             pauseExamInvalid = TRUE;
3776             forwardMostMove = pauseExamForwardMostMove;
3777             return;
3778         }
3779         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3780 #if ZIPPY
3781             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3782                 // [HGM] when we will receive the move list we now request, it will be
3783                 // fed to the engine from the first move on. So if the engine is not
3784                 // in the initial position now, bring it there.
3785                 InitChessProgram(&first, 0);
3786             }
3787 #endif
3788             ics_getting_history = H_REQUESTED;
3789             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3790             SendToICS(str);
3791         }
3792         forwardMostMove = backwardMostMove = currentMove = moveNum;
3793     }
3794     
3795     /* Update the clocks */
3796     if (strchr(elapsed_time, '.')) {
3797       /* Time is in ms */
3798       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3799       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3800     } else {
3801       /* Time is in seconds */
3802       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3803       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3804     }
3805       
3806
3807 #if ZIPPY
3808     if (appData.zippyPlay && newGame &&
3809         gameMode != IcsObserving && gameMode != IcsIdle &&
3810         gameMode != IcsExamining)
3811       ZippyFirstBoard(moveNum, basetime, increment);
3812 #endif
3813     
3814     /* Put the move on the move list, first converting
3815        to canonical algebraic form. */
3816     if (moveNum > 0) {
3817   if (appData.debugMode) {
3818     if (appData.debugMode) { int f = forwardMostMove;
3819         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3820                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3821                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3822     }
3823     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3824     fprintf(debugFP, "moveNum = %d\n", moveNum);
3825     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3826     setbuf(debugFP, NULL);
3827   }
3828         if (moveNum <= backwardMostMove) {
3829             /* We don't know what the board looked like before
3830                this move.  Punt. */
3831             strcpy(parseList[moveNum - 1], move_str);
3832             strcat(parseList[moveNum - 1], " ");
3833             strcat(parseList[moveNum - 1], elapsed_time);
3834             moveList[moveNum - 1][0] = NULLCHAR;
3835         } else if (strcmp(move_str, "none") == 0) {
3836             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3837             /* Again, we don't know what the board looked like;
3838                this is really the start of the game. */
3839             parseList[moveNum - 1][0] = NULLCHAR;
3840             moveList[moveNum - 1][0] = NULLCHAR;
3841             backwardMostMove = moveNum;
3842             startedFromSetupPosition = TRUE;
3843             fromX = fromY = toX = toY = -1;
3844         } else {
3845           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3846           //                 So we parse the long-algebraic move string in stead of the SAN move
3847           int valid; char buf[MSG_SIZ], *prom;
3848
3849           // str looks something like "Q/a1-a2"; kill the slash
3850           if(str[1] == '/') 
3851                 sprintf(buf, "%c%s", str[0], str+2);
3852           else  strcpy(buf, str); // might be castling
3853           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3854                 strcat(buf, prom); // long move lacks promo specification!
3855           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3856                 if(appData.debugMode) 
3857                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3858                 strcpy(move_str, buf);
3859           }
3860           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3861                                 &fromX, &fromY, &toX, &toY, &promoChar)
3862                || ParseOneMove(buf, moveNum - 1, &moveType,
3863                                 &fromX, &fromY, &toX, &toY, &promoChar);
3864           // end of long SAN patch
3865           if (valid) {
3866             (void) CoordsToAlgebraic(boards[moveNum - 1],
3867                                      PosFlags(moveNum - 1),
3868                                      fromY, fromX, toY, toX, promoChar,
3869                                      parseList[moveNum-1]);
3870             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3871               case MT_NONE:
3872               case MT_STALEMATE:
3873               default:
3874                 break;
3875               case MT_CHECK:
3876                 if(gameInfo.variant != VariantShogi)
3877                     strcat(parseList[moveNum - 1], "+");
3878                 break;
3879               case MT_CHECKMATE:
3880               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3881                 strcat(parseList[moveNum - 1], "#");
3882                 break;
3883             }
3884             strcat(parseList[moveNum - 1], " ");
3885             strcat(parseList[moveNum - 1], elapsed_time);
3886             /* currentMoveString is set as a side-effect of ParseOneMove */
3887             strcpy(moveList[moveNum - 1], currentMoveString);
3888             strcat(moveList[moveNum - 1], "\n");
3889           } else {
3890             /* Move from ICS was illegal!?  Punt. */
3891   if (appData.debugMode) {
3892     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3893     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3894   }
3895             strcpy(parseList[moveNum - 1], move_str);
3896             strcat(parseList[moveNum - 1], " ");
3897             strcat(parseList[moveNum - 1], elapsed_time);
3898             moveList[moveNum - 1][0] = NULLCHAR;
3899             fromX = fromY = toX = toY = -1;
3900           }
3901         }
3902   if (appData.debugMode) {
3903     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3904     setbuf(debugFP, NULL);
3905   }
3906
3907 #if ZIPPY
3908         /* Send move to chess program (BEFORE animating it). */
3909         if (appData.zippyPlay && !newGame && newMove && 
3910            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3911
3912             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3913                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3914                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3915                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3916                             move_str);
3917                     DisplayError(str, 0);
3918                 } else {
3919                     if (first.sendTime) {
3920                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3921                     }
3922                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3923                     if (firstMove && !bookHit) {
3924                         firstMove = FALSE;
3925                         if (first.useColors) {
3926                           SendToProgram(gameMode == IcsPlayingWhite ?
3927                                         "white\ngo\n" :
3928                                         "black\ngo\n", &first);
3929                         } else {
3930                           SendToProgram("go\n", &first);
3931                         }
3932                         first.maybeThinking = TRUE;
3933                     }
3934                 }
3935             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3936               if (moveList[moveNum - 1][0] == NULLCHAR) {
3937                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3938                 DisplayError(str, 0);
3939               } else {
3940                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3941                 SendMoveToProgram(moveNum - 1, &first);
3942               }
3943             }
3944         }
3945 #endif
3946     }
3947
3948     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3949         /* If move comes from a remote source, animate it.  If it
3950            isn't remote, it will have already been animated. */
3951         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3952             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3953         }
3954         if (!pausing && appData.highlightLastMove) {
3955             SetHighlights(fromX, fromY, toX, toY);
3956         }
3957     }
3958     
3959     /* Start the clocks */
3960     whiteFlag = blackFlag = FALSE;
3961     appData.clockMode = !(basetime == 0 && increment == 0);
3962     if (ticking == 0) {
3963       ics_clock_paused = TRUE;
3964       StopClocks();
3965     } else if (ticking == 1) {
3966       ics_clock_paused = FALSE;
3967     }
3968     if (gameMode == IcsIdle ||
3969         relation == RELATION_OBSERVING_STATIC ||
3970         relation == RELATION_EXAMINING ||
3971         ics_clock_paused)
3972       DisplayBothClocks();
3973     else
3974       StartClocks();
3975     
3976     /* Display opponents and material strengths */
3977     if (gameInfo.variant != VariantBughouse &&
3978         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3979         if (tinyLayout || smallLayout) {
3980             if(gameInfo.variant == VariantNormal)
3981                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3982                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3983                     basetime, increment);
3984             else
3985                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3986                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3987                     basetime, increment, (int) gameInfo.variant);
3988         } else {
3989             if(gameInfo.variant == VariantNormal)
3990                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3991                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3992                     basetime, increment);
3993             else
3994                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3995                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3996                     basetime, increment, VariantName(gameInfo.variant));
3997         }
3998         DisplayTitle(str);
3999   if (appData.debugMode) {
4000     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4001   }
4002     }
4003
4004    
4005     /* Display the board */
4006     if (!pausing && !appData.noGUI) {
4007       
4008       if (appData.premove)
4009           if (!gotPremove || 
4010              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4011              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4012               ClearPremoveHighlights();
4013
4014       DrawPosition(FALSE, boards[currentMove]);
4015       DisplayMove(moveNum - 1);
4016       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4017             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4018               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4019         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4020       }
4021     }
4022
4023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4024 #if ZIPPY
4025     if(bookHit) { // [HGM] book: simulate book reply
4026         static char bookMove[MSG_SIZ]; // a bit generous?
4027
4028         programStats.nodes = programStats.depth = programStats.time = 
4029         programStats.score = programStats.got_only_move = 0;
4030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4031
4032         strcpy(bookMove, "move ");
4033         strcat(bookMove, bookHit);
4034         HandleMachineMove(bookMove, &first);
4035     }
4036 #endif
4037 }
4038
4039 void
4040 GetMoveListEvent()
4041 {
4042     char buf[MSG_SIZ];
4043     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4044         ics_getting_history = H_REQUESTED;
4045         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4046         SendToICS(buf);
4047     }
4048 }
4049
4050 void
4051 AnalysisPeriodicEvent(force)
4052      int force;
4053 {
4054     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4055          && !force) || !appData.periodicUpdates)
4056       return;
4057
4058     /* Send . command to Crafty to collect stats */
4059     SendToProgram(".\n", &first);
4060
4061     /* Don't send another until we get a response (this makes
4062        us stop sending to old Crafty's which don't understand
4063        the "." command (sending illegal cmds resets node count & time,
4064        which looks bad)) */
4065     programStats.ok_to_send = 0;
4066 }
4067
4068 void ics_update_width(new_width)
4069         int new_width;
4070 {
4071         ics_printf("set width %d\n", new_width);
4072 }
4073
4074 void
4075 SendMoveToProgram(moveNum, cps)
4076      int moveNum;
4077      ChessProgramState *cps;
4078 {
4079     char buf[MSG_SIZ];
4080
4081     if (cps->useUsermove) {
4082       SendToProgram("usermove ", cps);
4083     }
4084     if (cps->useSAN) {
4085       char *space;
4086       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4087         int len = space - parseList[moveNum];
4088         memcpy(buf, parseList[moveNum], len);
4089         buf[len++] = '\n';
4090         buf[len] = NULLCHAR;
4091       } else {
4092         sprintf(buf, "%s\n", parseList[moveNum]);
4093       }
4094       SendToProgram(buf, cps);
4095     } else {
4096       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4097         AlphaRank(moveList[moveNum], 4);
4098         SendToProgram(moveList[moveNum], cps);
4099         AlphaRank(moveList[moveNum], 4); // and back
4100       } else
4101       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4102        * the engine. It would be nice to have a better way to identify castle 
4103        * moves here. */
4104       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4105                                                                          && cps->useOOCastle) {
4106         int fromX = moveList[moveNum][0] - AAA; 
4107         int fromY = moveList[moveNum][1] - ONE;
4108         int toX = moveList[moveNum][2] - AAA; 
4109         int toY = moveList[moveNum][3] - ONE;
4110         if((boards[moveNum][fromY][fromX] == WhiteKing 
4111             && boards[moveNum][toY][toX] == WhiteRook)
4112            || (boards[moveNum][fromY][fromX] == BlackKing 
4113                && boards[moveNum][toY][toX] == BlackRook)) {
4114           if(toX > fromX) SendToProgram("O-O\n", cps);
4115           else SendToProgram("O-O-O\n", cps);
4116         }
4117         else SendToProgram(moveList[moveNum], cps);
4118       }
4119       else SendToProgram(moveList[moveNum], cps);
4120       /* End of additions by Tord */
4121     }
4122
4123     /* [HGM] setting up the opening has brought engine in force mode! */
4124     /*       Send 'go' if we are in a mode where machine should play. */
4125     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4126         (gameMode == TwoMachinesPlay   ||
4127 #ifdef ZIPPY
4128          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4129 #endif
4130          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4131         SendToProgram("go\n", cps);
4132   if (appData.debugMode) {
4133     fprintf(debugFP, "(extra)\n");
4134   }
4135     }
4136     setboardSpoiledMachineBlack = 0;
4137 }
4138
4139 void
4140 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4141      ChessMove moveType;
4142      int fromX, fromY, toX, toY;
4143 {
4144     char user_move[MSG_SIZ];
4145
4146     switch (moveType) {
4147       default:
4148         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4149                 (int)moveType, fromX, fromY, toX, toY);
4150         DisplayError(user_move + strlen("say "), 0);
4151         break;
4152       case WhiteKingSideCastle:
4153       case BlackKingSideCastle:
4154       case WhiteQueenSideCastleWild:
4155       case BlackQueenSideCastleWild:
4156       /* PUSH Fabien */
4157       case WhiteHSideCastleFR:
4158       case BlackHSideCastleFR:
4159       /* POP Fabien */
4160         sprintf(user_move, "o-o\n");
4161         break;
4162       case WhiteQueenSideCastle:
4163       case BlackQueenSideCastle:
4164       case WhiteKingSideCastleWild:
4165       case BlackKingSideCastleWild:
4166       /* PUSH Fabien */
4167       case WhiteASideCastleFR:
4168       case BlackASideCastleFR:
4169       /* POP Fabien */
4170         sprintf(user_move, "o-o-o\n");
4171         break;
4172       case WhitePromotionQueen:
4173       case BlackPromotionQueen:
4174       case WhitePromotionRook:
4175       case BlackPromotionRook:
4176       case WhitePromotionBishop:
4177       case BlackPromotionBishop:
4178       case WhitePromotionKnight:
4179       case BlackPromotionKnight:
4180       case WhitePromotionKing:
4181       case BlackPromotionKing:
4182       case WhitePromotionChancellor:
4183       case BlackPromotionChancellor:
4184       case WhitePromotionArchbishop:
4185       case BlackPromotionArchbishop:
4186         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4187             sprintf(user_move, "%c%c%c%c=%c\n",
4188                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189                 PieceToChar(WhiteFerz));
4190         else if(gameInfo.variant == VariantGreat)
4191             sprintf(user_move, "%c%c%c%c=%c\n",
4192                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193                 PieceToChar(WhiteMan));
4194         else
4195             sprintf(user_move, "%c%c%c%c=%c\n",
4196                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4197                 PieceToChar(PromoPiece(moveType)));
4198         break;
4199       case WhiteDrop:
4200       case BlackDrop:
4201         sprintf(user_move, "%c@%c%c\n",
4202                 ToUpper(PieceToChar((ChessSquare) fromX)),
4203                 AAA + toX, ONE + toY);
4204         break;
4205       case NormalMove:
4206       case WhiteCapturesEnPassant:
4207       case BlackCapturesEnPassant:
4208       case IllegalMove:  /* could be a variant we don't quite understand */
4209         sprintf(user_move, "%c%c%c%c\n",
4210                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4211         break;
4212     }
4213     SendToICS(user_move);
4214     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4215         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4216 }
4217
4218 void
4219 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4220      int rf, ff, rt, ft;
4221      char promoChar;
4222      char move[7];
4223 {
4224     if (rf == DROP_RANK) {
4225         sprintf(move, "%c@%c%c\n",
4226                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4227     } else {
4228         if (promoChar == 'x' || promoChar == NULLCHAR) {
4229             sprintf(move, "%c%c%c%c\n",
4230                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4231         } else {
4232             sprintf(move, "%c%c%c%c%c\n",
4233                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4234         }
4235     }
4236 }
4237
4238 void
4239 ProcessICSInitScript(f)
4240      FILE *f;
4241 {
4242     char buf[MSG_SIZ];
4243
4244     while (fgets(buf, MSG_SIZ, f)) {
4245         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4246     }
4247
4248     fclose(f);
4249 }
4250
4251
4252 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4253 void
4254 AlphaRank(char *move, int n)
4255 {
4256 //    char *p = move, c; int x, y;
4257
4258     if (appData.debugMode) {
4259         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4260     }
4261
4262     if(move[1]=='*' && 
4263        move[2]>='0' && move[2]<='9' &&
4264        move[3]>='a' && move[3]<='x'    ) {
4265         move[1] = '@';
4266         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4267         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4268     } else
4269     if(move[0]>='0' && move[0]<='9' &&
4270        move[1]>='a' && move[1]<='x' &&
4271        move[2]>='0' && move[2]<='9' &&
4272        move[3]>='a' && move[3]<='x'    ) {
4273         /* input move, Shogi -> normal */
4274         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4275         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4276         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4277         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4278     } else
4279     if(move[1]=='@' &&
4280        move[3]>='0' && move[3]<='9' &&
4281        move[2]>='a' && move[2]<='x'    ) {
4282         move[1] = '*';
4283         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4284         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4285     } else
4286     if(
4287        move[0]>='a' && move[0]<='x' &&
4288        move[3]>='0' && move[3]<='9' &&
4289        move[2]>='a' && move[2]<='x'    ) {
4290          /* output move, normal -> Shogi */
4291         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4292         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4293         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4294         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4295         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4296     }
4297     if (appData.debugMode) {
4298         fprintf(debugFP, "   out = '%s'\n", move);
4299     }
4300 }
4301
4302 /* Parser for moves from gnuchess, ICS, or user typein box */
4303 Boolean
4304 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4305      char *move;
4306      int moveNum;
4307      ChessMove *moveType;
4308      int *fromX, *fromY, *toX, *toY;
4309      char *promoChar;
4310 {       
4311     if (appData.debugMode) {
4312         fprintf(debugFP, "move to parse: %s\n", move);
4313     }
4314     *moveType = yylexstr(moveNum, move);
4315
4316     switch (*moveType) {
4317       case WhitePromotionChancellor:
4318       case BlackPromotionChancellor:
4319       case WhitePromotionArchbishop:
4320       case BlackPromotionArchbishop:
4321       case WhitePromotionQueen:
4322       case BlackPromotionQueen:
4323       case WhitePromotionRook:
4324       case BlackPromotionRook:
4325       case WhitePromotionBishop:
4326       case BlackPromotionBishop:
4327       case WhitePromotionKnight:
4328       case BlackPromotionKnight:
4329       case WhitePromotionKing:
4330       case BlackPromotionKing:
4331       case NormalMove:
4332       case WhiteCapturesEnPassant:
4333       case BlackCapturesEnPassant:
4334       case WhiteKingSideCastle:
4335       case WhiteQueenSideCastle:
4336       case BlackKingSideCastle:
4337       case BlackQueenSideCastle:
4338       case WhiteKingSideCastleWild:
4339       case WhiteQueenSideCastleWild:
4340       case BlackKingSideCastleWild:
4341       case BlackQueenSideCastleWild:
4342       /* Code added by Tord: */
4343       case WhiteHSideCastleFR:
4344       case WhiteASideCastleFR:
4345       case BlackHSideCastleFR:
4346       case BlackASideCastleFR:
4347       /* End of code added by Tord */
4348       case IllegalMove:         /* bug or odd chess variant */
4349         *fromX = currentMoveString[0] - AAA;
4350         *fromY = currentMoveString[1] - ONE;
4351         *toX = currentMoveString[2] - AAA;
4352         *toY = currentMoveString[3] - ONE;
4353         *promoChar = currentMoveString[4];
4354         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4355             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4356     if (appData.debugMode) {
4357         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4358     }
4359             *fromX = *fromY = *toX = *toY = 0;
4360             return FALSE;
4361         }
4362         if (appData.testLegality) {
4363           return (*moveType != IllegalMove);
4364         } else {
4365           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4366                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4367         }
4368
4369       case WhiteDrop:
4370       case BlackDrop:
4371         *fromX = *moveType == WhiteDrop ?
4372           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4373           (int) CharToPiece(ToLower(currentMoveString[0]));
4374         *fromY = DROP_RANK;
4375         *toX = currentMoveString[2] - AAA;
4376         *toY = currentMoveString[3] - ONE;
4377         *promoChar = NULLCHAR;
4378         return TRUE;
4379
4380       case AmbiguousMove:
4381       case ImpossibleMove:
4382       case (ChessMove) 0:       /* end of file */
4383       case ElapsedTime:
4384       case Comment:
4385       case PGNTag:
4386       case NAG:
4387       case WhiteWins:
4388       case BlackWins:
4389       case GameIsDrawn:
4390       default:
4391     if (appData.debugMode) {
4392         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4393     }
4394         /* bug? */
4395         *fromX = *fromY = *toX = *toY = 0;
4396         *promoChar = NULLCHAR;
4397         return FALSE;
4398     }
4399 }
4400
4401
4402 void
4403 ParsePV(char *pv)
4404 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4405   int fromX, fromY, toX, toY; char promoChar;
4406   ChessMove moveType;
4407   Boolean valid;
4408   int nr = 0;
4409
4410   endPV = forwardMostMove;
4411   do {
4412     while(*pv == ' ') pv++;
4413     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4414     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4415 if(appData.debugMode){
4416 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4417 }
4418     if(!valid && nr == 0 &&
4419        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4420         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4421     }
4422     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4423     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4424     nr++;
4425     if(endPV+1 > framePtr) break; // no space, truncate
4426     if(!valid) break;
4427     endPV++;
4428     CopyBoard(boards[endPV], boards[endPV-1]);
4429     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4430     moveList[endPV-1][0] = fromX + AAA;
4431     moveList[endPV-1][1] = fromY + ONE;
4432     moveList[endPV-1][2] = toX + AAA;
4433     moveList[endPV-1][3] = toY + ONE;
4434     parseList[endPV-1][0] = NULLCHAR;
4435   } while(valid);
4436 }
4437
4438 static int lastX, lastY;
4439
4440 Boolean
4441 LoadPV(int x, int y)
4442 { // called on right mouse click to load PV
4443   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4444   lastX = x; lastY = y;
4445   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4446   currentMove = endPV;
4447   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4448   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4449                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4450   DrawPosition(TRUE, boards[currentMove]);
4451   return TRUE;
4452 }
4453
4454 void
4455 UnLoadPV()
4456 {
4457   if(endPV < 0) return;
4458   endPV = -1;
4459   currentMove = forwardMostMove;
4460   ClearPremoveHighlights();
4461   DrawPosition(TRUE, boards[currentMove]);
4462 }
4463
4464 void
4465 MovePV(int x, int y, int h)
4466 { // step through PV based on mouse coordinates (called on mouse move)
4467   int margin = h>>3, step = 0;
4468
4469   if(endPV < 0) return;
4470   // we must somehow check if right button is still down (might be released off board!)
4471   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4472   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4473   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4474   if(!step) return;
4475   lastX = x; lastY = y;
4476   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4477   currentMove += step;
4478   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4479   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4480                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4481   DrawPosition(FALSE, boards[currentMove]);
4482 }
4483
4484
4485 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4486 // All positions will have equal probability, but the current method will not provide a unique
4487 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4488 #define DARK 1
4489 #define LITE 2
4490 #define ANY 3
4491
4492 int squaresLeft[4];
4493 int piecesLeft[(int)BlackPawn];
4494 int seed, nrOfShuffles;
4495
4496 void GetPositionNumber()
4497 {       // sets global variable seed
4498         int i;
4499
4500         seed = appData.defaultFrcPosition;
4501         if(seed < 0) { // randomize based on time for negative FRC position numbers
4502                 for(i=0; i<50; i++) seed += random();
4503                 seed = random() ^ random() >> 8 ^ random() << 8;
4504                 if(seed<0) seed = -seed;
4505         }
4506 }
4507
4508 int put(Board board, int pieceType, int rank, int n, int shade)
4509 // put the piece on the (n-1)-th empty squares of the given shade
4510 {
4511         int i;
4512
4513         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4514                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4515                         board[rank][i] = (ChessSquare) pieceType;
4516                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4517                         squaresLeft[ANY]--;
4518                         piecesLeft[pieceType]--; 
4519                         return i;
4520                 }
4521         }
4522         return -1;
4523 }
4524
4525
4526 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4527 // calculate where the next piece goes, (any empty square), and put it there
4528 {
4529         int i;
4530
4531         i = seed % squaresLeft[shade];
4532         nrOfShuffles *= squaresLeft[shade];
4533         seed /= squaresLeft[shade];
4534         put(board, pieceType, rank, i, shade);
4535 }
4536
4537 void AddTwoPieces(Board board, int pieceType, int rank)
4538 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4539 {
4540         int i, n=squaresLeft[ANY], j=n-1, k;
4541
4542         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4543         i = seed % k;  // pick one
4544         nrOfShuffles *= k;
4545         seed /= k;
4546         while(i >= j) i -= j--;
4547         j = n - 1 - j; i += j;
4548         put(board, pieceType, rank, j, ANY);
4549         put(board, pieceType, rank, i, ANY);
4550 }
4551
4552 void SetUpShuffle(Board board, int number)
4553 {
4554         int i, p, first=1;
4555
4556         GetPositionNumber(); nrOfShuffles = 1;
4557
4558         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4559         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4560         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4561
4562         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4563
4564         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4565             p = (int) board[0][i];
4566             if(p < (int) BlackPawn) piecesLeft[p] ++;
4567             board[0][i] = EmptySquare;
4568         }
4569
4570         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4571             // shuffles restricted to allow normal castling put KRR first
4572             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4573                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4574             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4575                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4576             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4577                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4578             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4579                 put(board, WhiteRook, 0, 0, ANY);
4580             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4581         }
4582
4583         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4584             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4585             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4586                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4587                 while(piecesLeft[p] >= 2) {
4588                     AddOnePiece(board, p, 0, LITE);
4589                     AddOnePiece(board, p, 0, DARK);
4590                 }
4591                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4592             }
4593
4594         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4595             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4596             // but we leave King and Rooks for last, to possibly obey FRC restriction
4597             if(p == (int)WhiteRook) continue;
4598             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4599             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4600         }
4601
4602         // now everything is placed, except perhaps King (Unicorn) and Rooks
4603
4604         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4605             // Last King gets castling rights
4606             while(piecesLeft[(int)WhiteUnicorn]) {
4607                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4608                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4609             }
4610
4611             while(piecesLeft[(int)WhiteKing]) {
4612                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4613                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4614             }
4615
4616
4617         } else {
4618             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4619             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4620         }
4621
4622         // Only Rooks can be left; simply place them all
4623         while(piecesLeft[(int)WhiteRook]) {
4624                 i = put(board, WhiteRook, 0, 0, ANY);
4625                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4626                         if(first) {
4627                                 first=0;
4628                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4629                         }
4630                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4631                 }
4632         }
4633         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4634             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4635         }
4636
4637         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4638 }
4639
4640 int SetCharTable( char *table, const char * map )
4641 /* [HGM] moved here from winboard.c because of its general usefulness */
4642 /*       Basically a safe strcpy that uses the last character as King */
4643 {
4644     int result = FALSE; int NrPieces;
4645
4646     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4647                     && NrPieces >= 12 && !(NrPieces&1)) {
4648         int i; /* [HGM] Accept even length from 12 to 34 */
4649
4650         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4651         for( i=0; i<NrPieces/2-1; i++ ) {
4652             table[i] = map[i];
4653             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4654         }
4655         table[(int) WhiteKing]  = map[NrPieces/2-1];
4656         table[(int) BlackKing]  = map[NrPieces-1];
4657
4658         result = TRUE;
4659     }
4660
4661     return result;
4662 }
4663
4664 void Prelude(Board board)
4665 {       // [HGM] superchess: random selection of exo-pieces
4666         int i, j, k; ChessSquare p; 
4667         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4668
4669         GetPositionNumber(); // use FRC position number
4670
4671         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4672             SetCharTable(pieceToChar, appData.pieceToCharTable);
4673             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4674                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4675         }
4676
4677         j = seed%4;                 seed /= 4; 
4678         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4679         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4680         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4681         j = seed%3 + (seed%3 >= j); seed /= 3; 
4682         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4683         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4684         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4685         j = seed%3;                 seed /= 3; 
4686         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4687         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4688         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4689         j = seed%2 + (seed%2 >= j); seed /= 2; 
4690         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4691         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4692         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4693         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4694         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4695         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4696         put(board, exoPieces[0],    0, 0, ANY);
4697         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4698 }
4699
4700 void
4701 InitPosition(redraw)
4702      int redraw;
4703 {
4704     ChessSquare (* pieces)[BOARD_FILES];
4705     int i, j, pawnRow, overrule,
4706     oldx = gameInfo.boardWidth,
4707     oldy = gameInfo.boardHeight,
4708     oldh = gameInfo.holdingsWidth,
4709     oldv = gameInfo.variant;
4710
4711     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4712
4713     /* [AS] Initialize pv info list [HGM] and game status */
4714     {
4715         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4716             pvInfoList[i].depth = 0;
4717             boards[i][EP_STATUS] = EP_NONE;
4718             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4719         }
4720
4721         initialRulePlies = 0; /* 50-move counter start */
4722
4723         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4724         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4725     }
4726
4727     
4728     /* [HGM] logic here is completely changed. In stead of full positions */
4729     /* the initialized data only consist of the two backranks. The switch */
4730     /* selects which one we will use, which is than copied to the Board   */
4731     /* initialPosition, which for the rest is initialized by Pawns and    */
4732     /* empty squares. This initial position is then copied to boards[0],  */
4733     /* possibly after shuffling, so that it remains available.            */
4734
4735     gameInfo.holdingsWidth = 0; /* default board sizes */
4736     gameInfo.boardWidth    = 8;
4737     gameInfo.boardHeight   = 8;
4738     gameInfo.holdingsSize  = 0;
4739     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4740     for(i=0; i<BOARD_FILES-2; i++)
4741       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4742     initialPosition[EP_STATUS] = EP_NONE;
4743     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4744
4745     switch (gameInfo.variant) {
4746     case VariantFischeRandom:
4747       shuffleOpenings = TRUE;
4748     default:
4749       pieces = FIDEArray;
4750       break;
4751     case VariantShatranj:
4752       pieces = ShatranjArray;
4753       nrCastlingRights = 0;
4754       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4755       break;
4756     case VariantTwoKings:
4757       pieces = twoKingsArray;
4758       break;
4759     case VariantCapaRandom:
4760       shuffleOpenings = TRUE;
4761     case VariantCapablanca:
4762       pieces = CapablancaArray;
4763       gameInfo.boardWidth = 10;
4764       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4765       break;
4766     case VariantGothic:
4767       pieces = GothicArray;
4768       gameInfo.boardWidth = 10;
4769       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4770       break;
4771     case VariantJanus:
4772       pieces = JanusArray;
4773       gameInfo.boardWidth = 10;
4774       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4775       nrCastlingRights = 6;
4776         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4777         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4778         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4779         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4780         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4781         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4782       break;
4783     case VariantFalcon:
4784       pieces = FalconArray;
4785       gameInfo.boardWidth = 10;
4786       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4787       break;
4788     case VariantXiangqi:
4789       pieces = XiangqiArray;
4790       gameInfo.boardWidth  = 9;
4791       gameInfo.boardHeight = 10;
4792       nrCastlingRights = 0;
4793       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4794       break;
4795     case VariantShogi:
4796       pieces = ShogiArray;
4797       gameInfo.boardWidth  = 9;
4798       gameInfo.boardHeight = 9;
4799       gameInfo.holdingsSize = 7;
4800       nrCastlingRights = 0;
4801       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4802       break;
4803     case VariantCourier:
4804       pieces = CourierArray;
4805       gameInfo.boardWidth  = 12;
4806       nrCastlingRights = 0;
4807       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4808       break;
4809     case VariantKnightmate:
4810       pieces = KnightmateArray;
4811       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4812       break;
4813     case VariantFairy:
4814       pieces = fairyArray;
4815       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4816       break;
4817     case VariantGreat:
4818       pieces = GreatArray;
4819       gameInfo.boardWidth = 10;
4820       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4821       gameInfo.holdingsSize = 8;
4822       break;
4823     case VariantSuper:
4824       pieces = FIDEArray;
4825       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4826       gameInfo.holdingsSize = 8;
4827       startedFromSetupPosition = TRUE;
4828       break;
4829     case VariantCrazyhouse:
4830     case VariantBughouse:
4831       pieces = FIDEArray;
4832       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4833       gameInfo.holdingsSize = 5;
4834       break;
4835     case VariantWildCastle:
4836       pieces = FIDEArray;
4837       /* !!?shuffle with kings guaranteed to be on d or e file */
4838       shuffleOpenings = 1;
4839       break;
4840     case VariantNoCastle:
4841       pieces = FIDEArray;
4842       nrCastlingRights = 0;
4843       /* !!?unconstrained back-rank shuffle */
4844       shuffleOpenings = 1;
4845       break;
4846     }
4847
4848     overrule = 0;
4849     if(appData.NrFiles >= 0) {
4850         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4851         gameInfo.boardWidth = appData.NrFiles;
4852     }
4853     if(appData.NrRanks >= 0) {
4854         gameInfo.boardHeight = appData.NrRanks;
4855     }
4856     if(appData.holdingsSize >= 0) {
4857         i = appData.holdingsSize;
4858         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4859         gameInfo.holdingsSize = i;
4860     }
4861     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4862     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4863         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4864
4865     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4866     if(pawnRow < 1) pawnRow = 1;
4867
4868     /* User pieceToChar list overrules defaults */
4869     if(appData.pieceToCharTable != NULL)
4870         SetCharTable(pieceToChar, appData.pieceToCharTable);
4871
4872     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4873
4874         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4875             s = (ChessSquare) 0; /* account holding counts in guard band */
4876         for( i=0; i<BOARD_HEIGHT; i++ )
4877             initialPosition[i][j] = s;
4878
4879         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4880         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4881         initialPosition[pawnRow][j] = WhitePawn;
4882         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4883         if(gameInfo.variant == VariantXiangqi) {
4884             if(j&1) {
4885                 initialPosition[pawnRow][j] = 
4886                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4887                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4888                    initialPosition[2][j] = WhiteCannon;
4889                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4890                 }
4891             }
4892         }
4893         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4894     }
4895     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4896
4897             j=BOARD_LEFT+1;
4898             initialPosition[1][j] = WhiteBishop;
4899             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4900             j=BOARD_RGHT-2;
4901             initialPosition[1][j] = WhiteRook;
4902             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4903     }
4904
4905     if( nrCastlingRights == -1) {
4906         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4907         /*       This sets default castling rights from none to normal corners   */
4908         /* Variants with other castling rights must set them themselves above    */
4909         nrCastlingRights = 6;
4910        
4911         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4912         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4913         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4914         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4915         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4916         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4917      }
4918
4919      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4920      if(gameInfo.variant == VariantGreat) { // promotion commoners
4921         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4922         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4923         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4924         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4925      }
4926   if (appData.debugMode) {
4927     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4928   }
4929     if(shuffleOpenings) {
4930         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4931         startedFromSetupPosition = TRUE;
4932     }
4933     if(startedFromPositionFile) {
4934       /* [HGM] loadPos: use PositionFile for every new game */
4935       CopyBoard(initialPosition, filePosition);
4936       for(i=0; i<nrCastlingRights; i++)
4937           initialRights[i] = filePosition[CASTLING][i];
4938       startedFromSetupPosition = TRUE;
4939     }
4940
4941     CopyBoard(boards[0], initialPosition);
4942
4943     if(oldx != gameInfo.boardWidth ||
4944        oldy != gameInfo.boardHeight ||
4945        oldh != gameInfo.holdingsWidth
4946 #ifdef GOTHIC
4947        || oldv == VariantGothic ||        // For licensing popups
4948        gameInfo.variant == VariantGothic
4949 #endif
4950 #ifdef FALCON
4951        || oldv == VariantFalcon ||
4952        gameInfo.variant == VariantFalcon
4953 #endif
4954                                          )
4955             InitDrawingSizes(-2 ,0);
4956
4957     if (redraw)
4958       DrawPosition(TRUE, boards[currentMove]);
4959 }
4960
4961 void
4962 SendBoard(cps, moveNum)
4963      ChessProgramState *cps;
4964      int moveNum;
4965 {
4966     char message[MSG_SIZ];
4967     
4968     if (cps->useSetboard) {
4969       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4970       sprintf(message, "setboard %s\n", fen);
4971       SendToProgram(message, cps);
4972       free(fen);
4973
4974     } else {
4975       ChessSquare *bp;
4976       int i, j;
4977       /* Kludge to set black to move, avoiding the troublesome and now
4978        * deprecated "black" command.
4979        */
4980       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4981
4982       SendToProgram("edit\n", cps);
4983       SendToProgram("#\n", cps);
4984       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4985         bp = &boards[moveNum][i][BOARD_LEFT];
4986         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4987           if ((int) *bp < (int) BlackPawn) {
4988             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4989                     AAA + j, ONE + i);
4990             if(message[0] == '+' || message[0] == '~') {
4991                 sprintf(message, "%c%c%c+\n",
4992                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4993                         AAA + j, ONE + i);
4994             }
4995             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4996                 message[1] = BOARD_RGHT   - 1 - j + '1';
4997                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4998             }
4999             SendToProgram(message, cps);
5000           }
5001         }
5002       }
5003     
5004       SendToProgram("c\n", cps);
5005       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5006         bp = &boards[moveNum][i][BOARD_LEFT];
5007         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5008           if (((int) *bp != (int) EmptySquare)
5009               && ((int) *bp >= (int) BlackPawn)) {
5010             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5011                     AAA + j, ONE + i);
5012             if(message[0] == '+' || message[0] == '~') {
5013                 sprintf(message, "%c%c%c+\n",
5014                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5015                         AAA + j, ONE + i);
5016             }
5017             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5018                 message[1] = BOARD_RGHT   - 1 - j + '1';
5019                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5020             }
5021             SendToProgram(message, cps);
5022           }
5023         }
5024       }
5025     
5026       SendToProgram(".\n", cps);
5027     }
5028     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5029 }
5030
5031 int
5032 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5033 {
5034     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5035     /* [HGM] add Shogi promotions */
5036     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5037     ChessSquare piece;
5038     ChessMove moveType;
5039     Boolean premove;
5040
5041     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5042     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5043
5044     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5045       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5046         return FALSE;
5047
5048     piece = boards[currentMove][fromY][fromX];
5049     if(gameInfo.variant == VariantShogi) {
5050         promotionZoneSize = 3;
5051         highestPromotingPiece = (int)WhiteFerz;
5052     }
5053
5054     // next weed out all moves that do not touch the promotion zone at all
5055     if((int)piece >= BlackPawn) {
5056         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5057              return FALSE;
5058         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5059     } else {
5060         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5061            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5062     }
5063
5064     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5065
5066     // weed out mandatory Shogi promotions
5067     if(gameInfo.variant == VariantShogi) {
5068         if(piece >= BlackPawn) {
5069             if(toY == 0 && piece == BlackPawn ||
5070                toY == 0 && piece == BlackQueen ||
5071                toY <= 1 && piece == BlackKnight) {
5072                 *promoChoice = '+';
5073                 return FALSE;
5074             }
5075         } else {
5076             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5077                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5078                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5079                 *promoChoice = '+';
5080                 return FALSE;
5081             }
5082         }
5083     }
5084
5085     // weed out obviously illegal Pawn moves
5086     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5087         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5088         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5089         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5090         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5091         // note we are not allowed to test for valid (non-)capture, due to premove
5092     }
5093
5094     // we either have a choice what to promote to, or (in Shogi) whether to promote
5095     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5096         *promoChoice = PieceToChar(BlackFerz);  // no choice
5097         return FALSE;
5098     }
5099     if(appData.alwaysPromoteToQueen) { // predetermined
5100         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5101              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5102         else *promoChoice = PieceToChar(BlackQueen);
5103         return FALSE;
5104     }
5105
5106     // suppress promotion popup on illegal moves that are not premoves
5107     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5108               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5109     if(appData.testLegality && !premove) {
5110         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5111                         fromY, fromX, toY, toX, NULLCHAR);
5112         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5113            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5114             return FALSE;
5115     }
5116
5117     return TRUE;
5118 }
5119
5120 int
5121 InPalace(row, column)
5122      int row, column;
5123 {   /* [HGM] for Xiangqi */
5124     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5125          column < (BOARD_WIDTH + 4)/2 &&
5126          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5127     return FALSE;
5128 }
5129
5130 int
5131 PieceForSquare (x, y)
5132      int x;
5133      int y;
5134 {
5135   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5136      return -1;
5137   else
5138      return boards[currentMove][y][x];
5139 }
5140
5141 int
5142 OKToStartUserMove(x, y)
5143      int x, y;
5144 {
5145     ChessSquare from_piece;
5146     int white_piece;
5147
5148     if (matchMode) return FALSE;
5149     if (gameMode == EditPosition) return TRUE;
5150
5151     if (x >= 0 && y >= 0)
5152       from_piece = boards[currentMove][y][x];
5153     else
5154       from_piece = EmptySquare;
5155
5156     if (from_piece == EmptySquare) return FALSE;
5157
5158     white_piece = (int)from_piece >= (int)WhitePawn &&
5159       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5160
5161     switch (gameMode) {
5162       case PlayFromGameFile:
5163       case AnalyzeFile:
5164       case TwoMachinesPlay:
5165       case EndOfGame:
5166         return FALSE;
5167
5168       case IcsObserving:
5169       case IcsIdle:
5170         return FALSE;
5171
5172       case MachinePlaysWhite:
5173       case IcsPlayingBlack:
5174         if (appData.zippyPlay) return FALSE;
5175         if (white_piece) {
5176             DisplayMoveError(_("You are playing Black"));
5177             return FALSE;
5178         }
5179         break;
5180
5181       case MachinePlaysBlack:
5182       case IcsPlayingWhite:
5183         if (appData.zippyPlay) return FALSE;
5184         if (!white_piece) {
5185             DisplayMoveError(_("You are playing White"));
5186             return FALSE;
5187         }
5188         break;
5189
5190       case EditGame:
5191         if (!white_piece && WhiteOnMove(currentMove)) {
5192             DisplayMoveError(_("It is White's turn"));
5193             return FALSE;
5194         }           
5195         if (white_piece && !WhiteOnMove(currentMove)) {
5196             DisplayMoveError(_("It is Black's turn"));
5197             return FALSE;
5198         }           
5199         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5200             /* Editing correspondence game history */
5201             /* Could disallow this or prompt for confirmation */
5202             cmailOldMove = -1;
5203         }
5204         break;
5205
5206       case BeginningOfGame:
5207         if (appData.icsActive) return FALSE;
5208         if (!appData.noChessProgram) {
5209             if (!white_piece) {
5210                 DisplayMoveError(_("You are playing White"));
5211                 return FALSE;
5212             }
5213         }
5214         break;
5215         
5216       case Training:
5217         if (!white_piece && WhiteOnMove(currentMove)) {
5218             DisplayMoveError(_("It is White's turn"));
5219             return FALSE;
5220         }           
5221         if (white_piece && !WhiteOnMove(currentMove)) {
5222             DisplayMoveError(_("It is Black's turn"));
5223             return FALSE;
5224         }           
5225         break;
5226
5227       default:
5228       case IcsExamining:
5229         break;
5230     }
5231     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5232         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5233         && gameMode != AnalyzeFile && gameMode != Training) {
5234         DisplayMoveError(_("Displayed position is not current"));
5235         return FALSE;
5236     }
5237     return TRUE;
5238 }
5239
5240 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5241 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5242 int lastLoadGameUseList = FALSE;
5243 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5244 ChessMove lastLoadGameStart = (ChessMove) 0;
5245
5246 ChessMove
5247 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5248      int fromX, fromY, toX, toY;
5249      int promoChar;
5250      Boolean captureOwn;
5251 {
5252     ChessMove moveType;
5253     ChessSquare pdown, pup;
5254
5255     /* Check if the user is playing in turn.  This is complicated because we
5256        let the user "pick up" a piece before it is his turn.  So the piece he
5257        tried to pick up may have been captured by the time he puts it down!
5258        Therefore we use the color the user is supposed to be playing in this
5259        test, not the color of the piece that is currently on the starting
5260        square---except in EditGame mode, where the user is playing both
5261        sides; fortunately there the capture race can't happen.  (It can
5262        now happen in IcsExamining mode, but that's just too bad.  The user
5263        will get a somewhat confusing message in that case.)
5264        */
5265
5266     switch (gameMode) {
5267       case PlayFromGameFile:
5268       case AnalyzeFile:
5269       case TwoMachinesPlay:
5270       case EndOfGame:
5271       case IcsObserving:
5272       case IcsIdle:
5273         /* We switched into a game mode where moves are not accepted,
5274            perhaps while the mouse button was down. */
5275         return ImpossibleMove;
5276
5277       case MachinePlaysWhite:
5278         /* User is moving for Black */
5279         if (WhiteOnMove(currentMove)) {
5280             DisplayMoveError(_("It is White's turn"));
5281             return ImpossibleMove;
5282         }
5283         break;
5284
5285       case MachinePlaysBlack:
5286         /* User is moving for White */
5287         if (!WhiteOnMove(currentMove)) {
5288             DisplayMoveError(_("It is Black's turn"));
5289             return ImpossibleMove;
5290         }
5291         break;
5292
5293       case EditGame:
5294       case IcsExamining:
5295       case BeginningOfGame:
5296       case AnalyzeMode:
5297       case Training:
5298         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5299             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5300             /* User is moving for Black */
5301             if (WhiteOnMove(currentMove)) {
5302                 DisplayMoveError(_("It is White's turn"));
5303                 return ImpossibleMove;
5304             }
5305         } else {
5306             /* User is moving for White */
5307             if (!WhiteOnMove(currentMove)) {
5308                 DisplayMoveError(_("It is Black's turn"));
5309                 return ImpossibleMove;
5310             }
5311         }
5312         break;
5313
5314       case IcsPlayingBlack:
5315         /* User is moving for Black */
5316         if (WhiteOnMove(currentMove)) {
5317             if (!appData.premove) {
5318                 DisplayMoveError(_("It is White's turn"));
5319             } else if (toX >= 0 && toY >= 0) {
5320                 premoveToX = toX;
5321                 premoveToY = toY;
5322                 premoveFromX = fromX;
5323                 premoveFromY = fromY;
5324                 premovePromoChar = promoChar;
5325                 gotPremove = 1;
5326                 if (appData.debugMode) 
5327                     fprintf(debugFP, "Got premove: fromX %d,"
5328                             "fromY %d, toX %d, toY %d\n",
5329                             fromX, fromY, toX, toY);
5330             }
5331             return ImpossibleMove;
5332         }
5333         break;
5334
5335       case IcsPlayingWhite:
5336         /* User is moving for White */
5337         if (!WhiteOnMove(currentMove)) {
5338             if (!appData.premove) {
5339                 DisplayMoveError(_("It is Black's turn"));
5340             } else if (toX >= 0 && toY >= 0) {
5341                 premoveToX = toX;
5342                 premoveToY = toY;
5343                 premoveFromX = fromX;
5344                 premoveFromY = fromY;
5345                 premovePromoChar = promoChar;
5346                 gotPremove = 1;
5347                 if (appData.debugMode) 
5348                     fprintf(debugFP, "Got premove: fromX %d,"
5349                             "fromY %d, toX %d, toY %d\n",
5350                             fromX, fromY, toX, toY);
5351             }
5352             return ImpossibleMove;
5353         }
5354         break;
5355
5356       default:
5357         break;
5358
5359       case EditPosition:
5360         /* EditPosition, empty square, or different color piece;
5361            click-click move is possible */
5362         if (toX == -2 || toY == -2) {
5363             boards[0][fromY][fromX] = EmptySquare;
5364             return AmbiguousMove;
5365         } else if (toX >= 0 && toY >= 0) {
5366             boards[0][toY][toX] = boards[0][fromY][fromX];
5367             boards[0][fromY][fromX] = EmptySquare;
5368             return AmbiguousMove;
5369         }
5370         return ImpossibleMove;
5371     }
5372
5373     if(toX < 0 || toY < 0) return ImpossibleMove;
5374     pdown = boards[currentMove][fromY][fromX];
5375     pup = boards[currentMove][toY][toX];
5376
5377     /* [HGM] If move started in holdings, it means a drop */
5378     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5379          if( pup != EmptySquare ) return ImpossibleMove;
5380          if(appData.testLegality) {
5381              /* it would be more logical if LegalityTest() also figured out
5382               * which drops are legal. For now we forbid pawns on back rank.
5383               * Shogi is on its own here...
5384               */
5385              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5386                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5387                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5388          }
5389          return WhiteDrop; /* Not needed to specify white or black yet */
5390     }
5391
5392     userOfferedDraw = FALSE;
5393         
5394     /* [HGM] always test for legality, to get promotion info */
5395     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5396                                          fromY, fromX, toY, toX, promoChar);
5397     /* [HGM] but possibly ignore an IllegalMove result */
5398     if (appData.testLegality) {
5399         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5400             DisplayMoveError(_("Illegal move"));
5401             return ImpossibleMove;
5402         }
5403     }
5404
5405     return moveType;
5406     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5407        function is made into one that returns an OK move type if FinishMove
5408        should be called. This to give the calling driver routine the
5409        opportunity to finish the userMove input with a promotion popup,
5410        without bothering the user with this for invalid or illegal moves */
5411
5412 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5413 }
5414
5415 /* Common tail of UserMoveEvent and DropMenuEvent */
5416 int
5417 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5418      ChessMove moveType;
5419      int fromX, fromY, toX, toY;
5420      /*char*/int promoChar;
5421 {
5422     char *bookHit = 0;
5423
5424     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5425         // [HGM] superchess: suppress promotions to non-available piece
5426         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5427         if(WhiteOnMove(currentMove)) {
5428             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5429         } else {
5430             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5431         }
5432     }
5433
5434     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5435        move type in caller when we know the move is a legal promotion */
5436     if(moveType == NormalMove && promoChar)
5437         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5438
5439     /* [HGM] convert drag-and-drop piece drops to standard form */
5440     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5441          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5442            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5443                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5444            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5445            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5446            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5447            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5448          fromY = DROP_RANK;
5449     }
5450
5451     /* [HGM] <popupFix> The following if has been moved here from
5452        UserMoveEvent(). Because it seemed to belong here (why not allow
5453        piece drops in training games?), and because it can only be
5454        performed after it is known to what we promote. */
5455     if (gameMode == Training) {
5456       /* compare the move played on the board to the next move in the
5457        * game. If they match, display the move and the opponent's response. 
5458        * If they don't match, display an error message.
5459        */
5460       int saveAnimate;
5461       Board testBoard;
5462       CopyBoard(testBoard, boards[currentMove]);
5463       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5464
5465       if (CompareBoards(testBoard, boards[currentMove+1])) {
5466         ForwardInner(currentMove+1);
5467
5468         /* Autoplay the opponent's response.
5469          * if appData.animate was TRUE when Training mode was entered,
5470          * the response will be animated.
5471          */
5472         saveAnimate = appData.animate;
5473         appData.animate = animateTraining;
5474         ForwardInner(currentMove+1);
5475         appData.animate = saveAnimate;
5476
5477         /* check for the end of the game */
5478         if (currentMove >= forwardMostMove) {
5479           gameMode = PlayFromGameFile;
5480           ModeHighlight();
5481           SetTrainingModeOff();
5482           DisplayInformation(_("End of game"));
5483         }
5484       } else {
5485         DisplayError(_("Incorrect move"), 0);
5486       }
5487       return 1;
5488     }
5489
5490   /* Ok, now we know that the move is good, so we can kill
5491      the previous line in Analysis Mode */
5492   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5493                                 && currentMove < forwardMostMove) {
5494     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5495   }
5496
5497   /* If we need the chess program but it's dead, restart it */
5498   ResurrectChessProgram();
5499
5500   /* A user move restarts a paused game*/
5501   if (pausing)
5502     PauseEvent();
5503
5504   thinkOutput[0] = NULLCHAR;
5505
5506   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5507
5508   if (gameMode == BeginningOfGame) {
5509     if (appData.noChessProgram) {
5510       gameMode = EditGame;
5511       SetGameInfo();
5512     } else {
5513       char buf[MSG_SIZ];
5514       gameMode = MachinePlaysBlack;
5515       StartClocks();
5516       SetGameInfo();
5517       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5518       DisplayTitle(buf);
5519       if (first.sendName) {
5520         sprintf(buf, "name %s\n", gameInfo.white);
5521         SendToProgram(buf, &first);
5522       }
5523       StartClocks();
5524     }
5525     ModeHighlight();
5526   }
5527
5528   /* Relay move to ICS or chess engine */
5529   if (appData.icsActive) {
5530     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5531         gameMode == IcsExamining) {
5532       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5533       ics_user_moved = 1;
5534     }
5535   } else {
5536     if (first.sendTime && (gameMode == BeginningOfGame ||
5537                            gameMode == MachinePlaysWhite ||
5538                            gameMode == MachinePlaysBlack)) {
5539       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5540     }
5541     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5542          // [HGM] book: if program might be playing, let it use book
5543         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5544         first.maybeThinking = TRUE;
5545     } else SendMoveToProgram(forwardMostMove-1, &first);
5546     if (currentMove == cmailOldMove + 1) {
5547       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5548     }
5549   }
5550
5551   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5552
5553   switch (gameMode) {
5554   case EditGame:
5555     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5556     case MT_NONE:
5557     case MT_CHECK:
5558       break;
5559     case MT_CHECKMATE:
5560     case MT_STAINMATE:
5561       if (WhiteOnMove(currentMove)) {
5562         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5563       } else {
5564         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5565       }
5566       break;
5567     case MT_STALEMATE:
5568       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5569       break;
5570     }
5571     break;
5572     
5573   case MachinePlaysBlack:
5574   case MachinePlaysWhite:
5575     /* disable certain menu options while machine is thinking */
5576     SetMachineThinkingEnables();
5577     break;
5578
5579   default:
5580     break;
5581   }
5582
5583   if(bookHit) { // [HGM] book: simulate book reply
5584         static char bookMove[MSG_SIZ]; // a bit generous?
5585
5586         programStats.nodes = programStats.depth = programStats.time = 
5587         programStats.score = programStats.got_only_move = 0;
5588         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5589
5590         strcpy(bookMove, "move ");
5591         strcat(bookMove, bookHit);
5592         HandleMachineMove(bookMove, &first);
5593   }
5594   return 1;
5595 }
5596
5597 void
5598 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5599      int fromX, fromY, toX, toY;
5600      int promoChar;
5601 {
5602     /* [HGM] This routine was added to allow calling of its two logical
5603        parts from other modules in the old way. Before, UserMoveEvent()
5604        automatically called FinishMove() if the move was OK, and returned
5605        otherwise. I separated the two, in order to make it possible to
5606        slip a promotion popup in between. But that it always needs two
5607        calls, to the first part, (now called UserMoveTest() ), and to
5608        FinishMove if the first part succeeded. Calls that do not need
5609        to do anything in between, can call this routine the old way. 
5610     */
5611     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5612 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5613     if(moveType == AmbiguousMove)
5614         DrawPosition(FALSE, boards[currentMove]);
5615     else if(moveType != ImpossibleMove && moveType != Comment)
5616         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5617 }
5618
5619 void LeftClick(ClickType clickType, int xPix, int yPix)
5620 {
5621     int x, y;
5622     Boolean saveAnimate;
5623     static int second = 0, promotionChoice = 0;
5624     char promoChoice = NULLCHAR;
5625
5626     if (clickType == Press) ErrorPopDown();
5627
5628     x = EventToSquare(xPix, BOARD_WIDTH);
5629     y = EventToSquare(yPix, BOARD_HEIGHT);
5630     if (!flipView && y >= 0) {
5631         y = BOARD_HEIGHT - 1 - y;
5632     }
5633     if (flipView && x >= 0) {
5634         x = BOARD_WIDTH - 1 - x;
5635     }
5636
5637     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5638         if(clickType == Release) return; // ignore upclick of click-click destination
5639         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5640         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5641         if(gameInfo.holdingsWidth && 
5642                 (WhiteOnMove(currentMove) 
5643                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5644                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5645             // click in right holdings, for determining promotion piece
5646             ChessSquare p = boards[currentMove][y][x];
5647             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5648             if(p != EmptySquare) {
5649                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5650                 fromX = fromY = -1;
5651                 return;
5652             }
5653         }
5654         DrawPosition(FALSE, boards[currentMove]);
5655         return;
5656     }
5657
5658     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5659     if(clickType == Press
5660             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5661               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5662               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5663         return;
5664
5665     if (fromX == -1) {
5666         if (clickType == Press) {
5667             /* First square */
5668             if (OKToStartUserMove(x, y)) {
5669                 fromX = x;
5670                 fromY = y;
5671                 second = 0;
5672                 DragPieceBegin(xPix, yPix);
5673                 if (appData.highlightDragging) {
5674                     SetHighlights(x, y, -1, -1);
5675                 }
5676             }
5677         }
5678         return;
5679     }
5680
5681     /* fromX != -1 */
5682     if (clickType == Press && gameMode != EditPosition) {
5683         ChessSquare fromP;
5684         ChessSquare toP;
5685         int frc;
5686
5687         // ignore off-board to clicks
5688         if(y < 0 || x < 0) return;
5689
5690         /* Check if clicking again on the same color piece */
5691         fromP = boards[currentMove][fromY][fromX];
5692         toP = boards[currentMove][y][x];
5693         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5694         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5695              WhitePawn <= toP && toP <= WhiteKing &&
5696              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5697              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5698             (BlackPawn <= fromP && fromP <= BlackKing && 
5699              BlackPawn <= toP && toP <= BlackKing &&
5700              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5701              !(fromP == BlackKing && toP == BlackRook && frc))) {
5702             /* Clicked again on same color piece -- changed his mind */
5703             second = (x == fromX && y == fromY);
5704             if (appData.highlightDragging) {
5705                 SetHighlights(x, y, -1, -1);
5706             } else {
5707                 ClearHighlights();
5708             }
5709             if (OKToStartUserMove(x, y)) {
5710                 fromX = x;
5711                 fromY = y;
5712                 DragPieceBegin(xPix, yPix);
5713             }
5714             return;
5715         }
5716         // ignore clicks on holdings
5717         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5718     }
5719
5720     if (clickType == Release && x == fromX && y == fromY) {
5721         DragPieceEnd(xPix, yPix);
5722         if (appData.animateDragging) {
5723             /* Undo animation damage if any */
5724             DrawPosition(FALSE, NULL);
5725         }
5726         if (second) {
5727             /* Second up/down in same square; just abort move */
5728             second = 0;
5729             fromX = fromY = -1;
5730             ClearHighlights();
5731             gotPremove = 0;
5732             ClearPremoveHighlights();
5733         } else {
5734             /* First upclick in same square; start click-click mode */
5735             SetHighlights(x, y, -1, -1);
5736         }
5737         return;
5738     }
5739
5740     /* we now have a different from- and (possibly off-board) to-square */
5741     /* Completed move */
5742     toX = x;
5743     toY = y;
5744     saveAnimate = appData.animate;
5745     if (clickType == Press) {
5746         /* Finish clickclick move */
5747         if (appData.animate || appData.highlightLastMove) {
5748             SetHighlights(fromX, fromY, toX, toY);
5749         } else {
5750             ClearHighlights();
5751         }
5752     } else {
5753         /* Finish drag move */
5754         if (appData.highlightLastMove) {
5755             SetHighlights(fromX, fromY, toX, toY);
5756         } else {
5757             ClearHighlights();
5758         }
5759         DragPieceEnd(xPix, yPix);
5760         /* Don't animate move and drag both */
5761         appData.animate = FALSE;
5762     }
5763
5764     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5765     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5766         ClearHighlights();
5767         fromX = fromY = -1;
5768         DrawPosition(TRUE, NULL);
5769         return;
5770     }
5771
5772     // off-board moves should not be highlighted
5773     if(x < 0 || x < 0) ClearHighlights();
5774
5775     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5776         SetHighlights(fromX, fromY, toX, toY);
5777         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5778             // [HGM] super: promotion to captured piece selected from holdings
5779             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5780             promotionChoice = TRUE;
5781             // kludge follows to temporarily execute move on display, without promoting yet
5782             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5783             boards[currentMove][toY][toX] = p;
5784             DrawPosition(FALSE, boards[currentMove]);
5785             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5786             boards[currentMove][toY][toX] = q;
5787             DisplayMessage("Click in holdings to choose piece", "");
5788             return;
5789         }
5790         PromotionPopUp();
5791     } else {
5792         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5793         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5794         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5795         fromX = fromY = -1;
5796     }
5797     appData.animate = saveAnimate;
5798     if (appData.animate || appData.animateDragging) {
5799         /* Undo animation damage if needed */
5800         DrawPosition(FALSE, NULL);
5801     }
5802 }
5803
5804 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5805 {
5806 //    char * hint = lastHint;
5807     FrontEndProgramStats stats;
5808
5809     stats.which = cps == &first ? 0 : 1;
5810     stats.depth = cpstats->depth;
5811     stats.nodes = cpstats->nodes;
5812     stats.score = cpstats->score;
5813     stats.time = cpstats->time;
5814     stats.pv = cpstats->movelist;
5815     stats.hint = lastHint;
5816     stats.an_move_index = 0;
5817     stats.an_move_count = 0;
5818
5819     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5820         stats.hint = cpstats->move_name;
5821         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5822         stats.an_move_count = cpstats->nr_moves;
5823     }
5824
5825     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5826
5827     SetProgramStats( &stats );
5828 }
5829
5830 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5831 {   // [HGM] book: this routine intercepts moves to simulate book replies
5832     char *bookHit = NULL;
5833
5834     //first determine if the incoming move brings opponent into his book
5835     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5836         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5837     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5838     if(bookHit != NULL && !cps->bookSuspend) {
5839         // make sure opponent is not going to reply after receiving move to book position
5840         SendToProgram("force\n", cps);
5841         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5842     }
5843     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5844     // now arrange restart after book miss
5845     if(bookHit) {
5846         // after a book hit we never send 'go', and the code after the call to this routine
5847         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5848         char buf[MSG_SIZ];
5849         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5850         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5851         SendToProgram(buf, cps);
5852         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5853     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5854         SendToProgram("go\n", cps);
5855         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5856     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5857         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5858             SendToProgram("go\n", cps); 
5859         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5860     }
5861     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5862 }
5863
5864 char *savedMessage;
5865 ChessProgramState *savedState;
5866 void DeferredBookMove(void)
5867 {
5868         if(savedState->lastPing != savedState->lastPong)
5869                     ScheduleDelayedEvent(DeferredBookMove, 10);
5870         else
5871         HandleMachineMove(savedMessage, savedState);
5872 }
5873
5874 void
5875 HandleMachineMove(message, cps)
5876      char *message;
5877      ChessProgramState *cps;
5878 {
5879     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5880     char realname[MSG_SIZ];
5881     int fromX, fromY, toX, toY;
5882     ChessMove moveType;
5883     char promoChar;
5884     char *p;
5885     int machineWhite;
5886     char *bookHit;
5887
5888     cps->userError = 0;
5889
5890 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5891     /*
5892      * Kludge to ignore BEL characters
5893      */
5894     while (*message == '\007') message++;
5895
5896     /*
5897      * [HGM] engine debug message: ignore lines starting with '#' character
5898      */
5899     if(cps->debug && *message == '#') return;
5900
5901     /*
5902      * Look for book output
5903      */
5904     if (cps == &first && bookRequested) {
5905         if (message[0] == '\t' || message[0] == ' ') {
5906             /* Part of the book output is here; append it */
5907             strcat(bookOutput, message);
5908             strcat(bookOutput, "  \n");
5909             return;
5910         } else if (bookOutput[0] != NULLCHAR) {
5911             /* All of book output has arrived; display it */
5912             char *p = bookOutput;
5913             while (*p != NULLCHAR) {
5914                 if (*p == '\t') *p = ' ';
5915                 p++;
5916             }
5917             DisplayInformation(bookOutput);
5918             bookRequested = FALSE;
5919             /* Fall through to parse the current output */
5920         }
5921     }
5922
5923     /*
5924      * Look for machine move.
5925      */
5926     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5927         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5928     {
5929         /* This method is only useful on engines that support ping */
5930         if (cps->lastPing != cps->lastPong) {
5931           if (gameMode == BeginningOfGame) {
5932             /* Extra move from before last new; ignore */
5933             if (appData.debugMode) {
5934                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5935             }
5936           } else {
5937             if (appData.debugMode) {
5938                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5939                         cps->which, gameMode);
5940             }
5941
5942             SendToProgram("undo\n", cps);
5943           }
5944           return;
5945         }
5946
5947         switch (gameMode) {
5948           case BeginningOfGame:
5949             /* Extra move from before last reset; ignore */
5950             if (appData.debugMode) {
5951                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5952             }
5953             return;
5954
5955           case EndOfGame:
5956           case IcsIdle:
5957           default:
5958             /* Extra move after we tried to stop.  The mode test is
5959                not a reliable way of detecting this problem, but it's
5960                the best we can do on engines that don't support ping.
5961             */
5962             if (appData.debugMode) {
5963                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5964                         cps->which, gameMode);
5965             }
5966             SendToProgram("undo\n", cps);
5967             return;
5968
5969           case MachinePlaysWhite:
5970           case IcsPlayingWhite:
5971             machineWhite = TRUE;
5972             break;
5973
5974           case MachinePlaysBlack:
5975           case IcsPlayingBlack:
5976             machineWhite = FALSE;
5977             break;
5978
5979           case TwoMachinesPlay:
5980             machineWhite = (cps->twoMachinesColor[0] == 'w');
5981             break;
5982         }
5983         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5984             if (appData.debugMode) {
5985                 fprintf(debugFP,
5986                         "Ignoring move out of turn by %s, gameMode %d"
5987                         ", forwardMost %d\n",
5988                         cps->which, gameMode, forwardMostMove);
5989             }
5990             return;
5991         }
5992
5993     if (appData.debugMode) { int f = forwardMostMove;
5994         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5995                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5996                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5997     }
5998         if(cps->alphaRank) AlphaRank(machineMove, 4);
5999         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6000                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6001             /* Machine move could not be parsed; ignore it. */
6002             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6003                     machineMove, cps->which);
6004             DisplayError(buf1, 0);
6005             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6006                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6007             if (gameMode == TwoMachinesPlay) {
6008               GameEnds(machineWhite ? BlackWins : WhiteWins,
6009                        buf1, GE_XBOARD);
6010             }
6011             return;
6012         }
6013
6014         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6015         /* So we have to redo legality test with true e.p. status here,  */
6016         /* to make sure an illegal e.p. capture does not slip through,   */
6017         /* to cause a forfeit on a justified illegal-move complaint      */
6018         /* of the opponent.                                              */
6019         if( gameMode==TwoMachinesPlay && appData.testLegality
6020             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6021                                                               ) {
6022            ChessMove moveType;
6023            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6024                              fromY, fromX, toY, toX, promoChar);
6025             if (appData.debugMode) {
6026                 int i;
6027                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6028                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6029                 fprintf(debugFP, "castling rights\n");
6030             }
6031             if(moveType == IllegalMove) {
6032                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6033                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6034                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6035                            buf1, GE_XBOARD);
6036                 return;
6037            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6038            /* [HGM] Kludge to handle engines that send FRC-style castling
6039               when they shouldn't (like TSCP-Gothic) */
6040            switch(moveType) {
6041              case WhiteASideCastleFR:
6042              case BlackASideCastleFR:
6043                toX+=2;
6044                currentMoveString[2]++;
6045                break;
6046              case WhiteHSideCastleFR:
6047              case BlackHSideCastleFR:
6048                toX--;
6049                currentMoveString[2]--;
6050                break;
6051              default: ; // nothing to do, but suppresses warning of pedantic compilers
6052            }
6053         }
6054         hintRequested = FALSE;
6055         lastHint[0] = NULLCHAR;
6056         bookRequested = FALSE;
6057         /* Program may be pondering now */
6058         cps->maybeThinking = TRUE;
6059         if (cps->sendTime == 2) cps->sendTime = 1;
6060         if (cps->offeredDraw) cps->offeredDraw--;
6061
6062 #if ZIPPY
6063         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6064             first.initDone) {
6065           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6066           ics_user_moved = 1;
6067           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6068                 char buf[3*MSG_SIZ];
6069
6070                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6071                         programStats.score / 100.,
6072                         programStats.depth,
6073                         programStats.time / 100.,
6074                         (unsigned int)programStats.nodes,
6075                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6076                         programStats.movelist);
6077                 SendToICS(buf);
6078 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6079           }
6080         }
6081 #endif
6082         /* currentMoveString is set as a side-effect of ParseOneMove */
6083         strcpy(machineMove, currentMoveString);
6084         strcat(machineMove, "\n");
6085         strcpy(moveList[forwardMostMove], machineMove);
6086
6087         /* [AS] Save move info and clear stats for next move */
6088         pvInfoList[ forwardMostMove ].score = programStats.score;
6089         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6090         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6091         ClearProgramStats();
6092         thinkOutput[0] = NULLCHAR;
6093         hiddenThinkOutputState = 0;
6094
6095         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6096
6097         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6098         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6099             int count = 0;
6100
6101             while( count < adjudicateLossPlies ) {
6102                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6103
6104                 if( count & 1 ) {
6105                     score = -score; /* Flip score for winning side */
6106                 }
6107
6108                 if( score > adjudicateLossThreshold ) {
6109                     break;
6110                 }
6111
6112                 count++;
6113             }
6114
6115             if( count >= adjudicateLossPlies ) {
6116                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6117
6118                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6119                     "Xboard adjudication", 
6120                     GE_XBOARD );
6121
6122                 return;
6123             }
6124         }
6125
6126         if( gameMode == TwoMachinesPlay ) {
6127           // [HGM] some adjudications useful with buggy engines
6128             int k, count = 0; static int bare = 1;
6129           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6130
6131
6132             if( appData.testLegality )
6133             {   /* [HGM] Some more adjudications for obstinate engines */
6134                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6135                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6136                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6137                 static int moveCount = 6;
6138                 ChessMove result;
6139                 char *reason = NULL;
6140
6141                 /* Count what is on board. */
6142                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6143                 {   ChessSquare p = boards[forwardMostMove][i][j];
6144                     int m=i;
6145
6146                     switch((int) p)
6147                     {   /* count B,N,R and other of each side */
6148                         case WhiteKing:
6149                         case BlackKing:
6150                              NrK++; break; // [HGM] atomic: count Kings
6151                         case WhiteKnight:
6152                              NrWN++; break;
6153                         case WhiteBishop:
6154                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6155                              bishopsColor |= 1 << ((i^j)&1);
6156                              NrWB++; break;
6157                         case BlackKnight:
6158                              NrBN++; break;
6159                         case BlackBishop:
6160                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6161                              bishopsColor |= 1 << ((i^j)&1);
6162                              NrBB++; break;
6163                         case WhiteRook:
6164                              NrWR++; break;
6165                         case BlackRook:
6166                              NrBR++; break;
6167                         case WhiteQueen:
6168                              NrWQ++; break;
6169                         case BlackQueen:
6170                              NrBQ++; break;
6171                         case EmptySquare: 
6172                              break;
6173                         case BlackPawn:
6174                              m = 7-i;
6175                         case WhitePawn:
6176                              PawnAdvance += m; NrPawns++;
6177                     }
6178                     NrPieces += (p != EmptySquare);
6179                     NrW += ((int)p < (int)BlackPawn);
6180                     if(gameInfo.variant == VariantXiangqi && 
6181                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6182                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6183                         NrW -= ((int)p < (int)BlackPawn);
6184                     }
6185                 }
6186
6187                 /* Some material-based adjudications that have to be made before stalemate test */
6188                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6189                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6190                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6191                      if(appData.checkMates) {
6192                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6193                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6194                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6195                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6196                          return;
6197                      }
6198                 }
6199
6200                 /* Bare King in Shatranj (loses) or Losers (wins) */
6201                 if( NrW == 1 || NrPieces - NrW == 1) {
6202                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6203                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6204                      if(appData.checkMates) {
6205                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6206                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6207                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6208                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6209                          return;
6210                      }
6211                   } else
6212                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6213                   {    /* bare King */
6214                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6215                         if(appData.checkMates) {
6216                             /* but only adjudicate if adjudication enabled */
6217                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6218                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6219                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6220                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6221                             return;
6222                         }
6223                   }
6224                 } else bare = 1;
6225
6226
6227             // don't wait for engine to announce game end if we can judge ourselves
6228             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6229               case MT_CHECK:
6230                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6231                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6232                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6233                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6234                             checkCnt++;
6235                         if(checkCnt >= 2) {
6236                             reason = "Xboard adjudication: 3rd check";
6237                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6238                             break;
6239                         }
6240                     }
6241                 }
6242               case MT_NONE:
6243               default:
6244                 break;
6245               case MT_STALEMATE:
6246               case MT_STAINMATE:
6247                 reason = "Xboard adjudication: Stalemate";
6248                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6249                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6250                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6251                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6252                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6253                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6254                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6255                                                                         EP_CHECKMATE : EP_WINS);
6256                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6257                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6258                 }
6259                 break;
6260               case MT_CHECKMATE:
6261                 reason = "Xboard adjudication: Checkmate";
6262                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6263                 break;
6264             }
6265
6266                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6267                     case EP_STALEMATE:
6268                         result = GameIsDrawn; break;
6269                     case EP_CHECKMATE:
6270                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6271                     case EP_WINS:
6272                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6273                     default:
6274                         result = (ChessMove) 0;
6275                 }
6276                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6277                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6278                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6279                     GameEnds( result, reason, GE_XBOARD );
6280                     return;
6281                 }
6282
6283                 /* Next absolutely insufficient mating material. */
6284                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6285                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6286                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6287                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6288                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6289
6290                      /* always flag draws, for judging claims */
6291                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6292
6293                      if(appData.materialDraws) {
6294                          /* but only adjudicate them if adjudication enabled */
6295                          SendToProgram("force\n", cps->other); // suppress reply
6296                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6297                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6298                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6299                          return;
6300                      }
6301                 }
6302
6303                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6304                 if(NrPieces == 4 && 
6305                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6306                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6307                    || NrWN==2 || NrBN==2     /* KNNK */
6308                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6309                   ) ) {
6310                      if(--moveCount < 0 && appData.trivialDraws)
6311                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6312                           SendToProgram("force\n", cps->other); // suppress reply
6313                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6314                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6315                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6316                           return;
6317                      }
6318                 } else moveCount = 6;
6319             }
6320           }
6321           
6322           if (appData.debugMode) { int i;
6323             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6324                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6325                     appData.drawRepeats);
6326             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6327               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6328             
6329           }
6330
6331                 /* Check for rep-draws */
6332                 count = 0;
6333                 for(k = forwardMostMove-2;
6334                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6335                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6336                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6337                     k-=2)
6338                 {   int rights=0;
6339                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6340                         /* compare castling rights */
6341                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6342                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6343                                 rights++; /* King lost rights, while rook still had them */
6344                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6345                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6346                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6347                                    rights++; /* but at least one rook lost them */
6348                         }
6349                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6350                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6351                                 rights++; 
6352                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6353                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6354                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6355                                    rights++;
6356                         }
6357                         if( rights == 0 && ++count > appData.drawRepeats-2
6358                             && appData.drawRepeats > 1) {
6359                              /* adjudicate after user-specified nr of repeats */
6360                              SendToProgram("force\n", cps->other); // suppress reply
6361                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6362                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6363                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6364                                 // [HGM] xiangqi: check for forbidden perpetuals
6365                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6366                                 for(m=forwardMostMove; m>k; m-=2) {
6367                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6368                                         ourPerpetual = 0; // the current mover did not always check
6369                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6370                                         hisPerpetual = 0; // the opponent did not always check
6371                                 }
6372                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6373                                                                         ourPerpetual, hisPerpetual);
6374                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6375                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6376                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6377                                     return;
6378                                 }
6379                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6380                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6381                                 // Now check for perpetual chases
6382                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6383                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6384                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6385                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6386                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6387                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6388                                         return;
6389                                     }
6390                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6391                                         break; // Abort repetition-checking loop.
6392                                 }
6393                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6394                              }
6395                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6396                              return;
6397                         }
6398                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6399                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6400                     }
6401                 }
6402
6403                 /* Now we test for 50-move draws. Determine ply count */
6404                 count = forwardMostMove;
6405                 /* look for last irreversble move */
6406                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6407                     count--;
6408                 /* if we hit starting position, add initial plies */
6409                 if( count == backwardMostMove )
6410                     count -= initialRulePlies;
6411                 count = forwardMostMove - count; 
6412                 if( count >= 100)
6413                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6414                          /* this is used to judge if draw claims are legal */
6415                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6416                          SendToProgram("force\n", cps->other); // suppress reply
6417                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6418                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6419                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6420                          return;
6421                 }
6422
6423                 /* if draw offer is pending, treat it as a draw claim
6424                  * when draw condition present, to allow engines a way to
6425                  * claim draws before making their move to avoid a race
6426                  * condition occurring after their move
6427                  */
6428                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6429                          char *p = NULL;
6430                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6431                              p = "Draw claim: 50-move rule";
6432                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6433                              p = "Draw claim: 3-fold repetition";
6434                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6435                              p = "Draw claim: insufficient mating material";
6436                          if( p != NULL ) {
6437                              SendToProgram("force\n", cps->other); // suppress reply
6438                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6439                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6440                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6441                              return;
6442                          }
6443                 }
6444
6445
6446                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6447                     SendToProgram("force\n", cps->other); // suppress reply
6448                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6449                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6450
6451                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6452
6453                     return;
6454                 }
6455         }
6456
6457         bookHit = NULL;
6458         if (gameMode == TwoMachinesPlay) {
6459             /* [HGM] relaying draw offers moved to after reception of move */
6460             /* and interpreting offer as claim if it brings draw condition */
6461             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6462                 SendToProgram("draw\n", cps->other);
6463             }
6464             if (cps->other->sendTime) {
6465                 SendTimeRemaining(cps->other,
6466                                   cps->other->twoMachinesColor[0] == 'w');
6467             }
6468             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6469             if (firstMove && !bookHit) {
6470                 firstMove = FALSE;
6471                 if (cps->other->useColors) {
6472                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6473                 }
6474                 SendToProgram("go\n", cps->other);
6475             }
6476             cps->other->maybeThinking = TRUE;
6477         }
6478
6479         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6480         
6481         if (!pausing && appData.ringBellAfterMoves) {
6482             RingBell();
6483         }
6484
6485         /* 
6486          * Reenable menu items that were disabled while
6487          * machine was thinking
6488          */
6489         if (gameMode != TwoMachinesPlay)
6490             SetUserThinkingEnables();
6491
6492         // [HGM] book: after book hit opponent has received move and is now in force mode
6493         // force the book reply into it, and then fake that it outputted this move by jumping
6494         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6495         if(bookHit) {
6496                 static char bookMove[MSG_SIZ]; // a bit generous?
6497
6498                 strcpy(bookMove, "move ");
6499                 strcat(bookMove, bookHit);
6500                 message = bookMove;
6501                 cps = cps->other;
6502                 programStats.nodes = programStats.depth = programStats.time = 
6503                 programStats.score = programStats.got_only_move = 0;
6504                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6505
6506                 if(cps->lastPing != cps->lastPong) {
6507                     savedMessage = message; // args for deferred call
6508                     savedState = cps;
6509                     ScheduleDelayedEvent(DeferredBookMove, 10);
6510                     return;
6511                 }
6512                 goto FakeBookMove;
6513         }
6514
6515         return;
6516     }
6517
6518     /* Set special modes for chess engines.  Later something general
6519      *  could be added here; for now there is just one kludge feature,
6520      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6521      *  when "xboard" is given as an interactive command.
6522      */
6523     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6524         cps->useSigint = FALSE;
6525         cps->useSigterm = FALSE;
6526     }
6527     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6528       ParseFeatures(message+8, cps);
6529       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6530     }
6531
6532     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6533      * want this, I was asked to put it in, and obliged.
6534      */
6535     if (!strncmp(message, "setboard ", 9)) {
6536         Board initial_position;
6537
6538         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6539
6540         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6541             DisplayError(_("Bad FEN received from engine"), 0);
6542             return ;
6543         } else {
6544            Reset(TRUE, FALSE);
6545            CopyBoard(boards[0], initial_position);
6546            initialRulePlies = FENrulePlies;
6547            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6548            else gameMode = MachinePlaysBlack;                 
6549            DrawPosition(FALSE, boards[currentMove]);
6550         }
6551         return;
6552     }
6553
6554     /*
6555      * Look for communication commands
6556      */
6557     if (!strncmp(message, "telluser ", 9)) {
6558         DisplayNote(message + 9);
6559         return;
6560     }
6561     if (!strncmp(message, "tellusererror ", 14)) {
6562         cps->userError = 1;
6563         DisplayError(message + 14, 0);
6564         return;
6565     }
6566     if (!strncmp(message, "tellopponent ", 13)) {
6567       if (appData.icsActive) {
6568         if (loggedOn) {
6569           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6570           SendToICS(buf1);
6571         }
6572       } else {
6573         DisplayNote(message + 13);
6574       }
6575       return;
6576     }
6577     if (!strncmp(message, "tellothers ", 11)) {
6578       if (appData.icsActive) {
6579         if (loggedOn) {
6580           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6581           SendToICS(buf1);
6582         }
6583       }
6584       return;
6585     }
6586     if (!strncmp(message, "tellall ", 8)) {
6587       if (appData.icsActive) {
6588         if (loggedOn) {
6589           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6590           SendToICS(buf1);
6591         }
6592       } else {
6593         DisplayNote(message + 8);
6594       }
6595       return;
6596     }
6597     if (strncmp(message, "warning", 7) == 0) {
6598         /* Undocumented feature, use tellusererror in new code */
6599         DisplayError(message, 0);
6600         return;
6601     }
6602     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6603         strcpy(realname, cps->tidy);
6604         strcat(realname, " query");
6605         AskQuestion(realname, buf2, buf1, cps->pr);
6606         return;
6607     }
6608     /* Commands from the engine directly to ICS.  We don't allow these to be 
6609      *  sent until we are logged on. Crafty kibitzes have been known to 
6610      *  interfere with the login process.
6611      */
6612     if (loggedOn) {
6613         if (!strncmp(message, "tellics ", 8)) {
6614             SendToICS(message + 8);
6615             SendToICS("\n");
6616             return;
6617         }
6618         if (!strncmp(message, "tellicsnoalias ", 15)) {
6619             SendToICS(ics_prefix);
6620             SendToICS(message + 15);
6621             SendToICS("\n");
6622             return;
6623         }
6624         /* The following are for backward compatibility only */
6625         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6626             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6627             SendToICS(ics_prefix);
6628             SendToICS(message);
6629             SendToICS("\n");
6630             return;
6631         }
6632     }
6633     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6634         return;
6635     }
6636     /*
6637      * If the move is illegal, cancel it and redraw the board.
6638      * Also deal with other error cases.  Matching is rather loose
6639      * here to accommodate engines written before the spec.
6640      */
6641     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6642         strncmp(message, "Error", 5) == 0) {
6643         if (StrStr(message, "name") || 
6644             StrStr(message, "rating") || StrStr(message, "?") ||
6645             StrStr(message, "result") || StrStr(message, "board") ||
6646             StrStr(message, "bk") || StrStr(message, "computer") ||
6647             StrStr(message, "variant") || StrStr(message, "hint") ||
6648             StrStr(message, "random") || StrStr(message, "depth") ||
6649             StrStr(message, "accepted")) {
6650             return;
6651         }
6652         if (StrStr(message, "protover")) {
6653           /* Program is responding to input, so it's apparently done
6654              initializing, and this error message indicates it is
6655              protocol version 1.  So we don't need to wait any longer
6656              for it to initialize and send feature commands. */
6657           FeatureDone(cps, 1);
6658           cps->protocolVersion = 1;
6659           return;
6660         }
6661         cps->maybeThinking = FALSE;
6662
6663         if (StrStr(message, "draw")) {
6664             /* Program doesn't have "draw" command */
6665             cps->sendDrawOffers = 0;
6666             return;
6667         }
6668         if (cps->sendTime != 1 &&
6669             (StrStr(message, "time") || StrStr(message, "otim"))) {
6670           /* Program apparently doesn't have "time" or "otim" command */
6671           cps->sendTime = 0;
6672           return;
6673         }
6674         if (StrStr(message, "analyze")) {
6675             cps->analysisSupport = FALSE;
6676             cps->analyzing = FALSE;
6677             Reset(FALSE, TRUE);
6678             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6679             DisplayError(buf2, 0);
6680             return;
6681         }
6682         if (StrStr(message, "(no matching move)st")) {
6683           /* Special kludge for GNU Chess 4 only */
6684           cps->stKludge = TRUE;
6685           SendTimeControl(cps, movesPerSession, timeControl,
6686                           timeIncrement, appData.searchDepth,
6687                           searchTime);
6688           return;
6689         }
6690         if (StrStr(message, "(no matching move)sd")) {
6691           /* Special kludge for GNU Chess 4 only */
6692           cps->sdKludge = TRUE;
6693           SendTimeControl(cps, movesPerSession, timeControl,
6694                           timeIncrement, appData.searchDepth,
6695                           searchTime);
6696           return;
6697         }
6698         if (!StrStr(message, "llegal")) {
6699             return;
6700         }
6701         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6702             gameMode == IcsIdle) return;
6703         if (forwardMostMove <= backwardMostMove) return;
6704         if (pausing) PauseEvent();
6705       if(appData.forceIllegal) {
6706             // [HGM] illegal: machine refused move; force position after move into it
6707           SendToProgram("force\n", cps);
6708           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6709                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6710                 // when black is to move, while there might be nothing on a2 or black
6711                 // might already have the move. So send the board as if white has the move.
6712                 // But first we must change the stm of the engine, as it refused the last move
6713                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6714                 if(WhiteOnMove(forwardMostMove)) {
6715                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6716                     SendBoard(cps, forwardMostMove); // kludgeless board
6717                 } else {
6718                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6719                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6720                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6721                 }
6722           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6723             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6724                  gameMode == TwoMachinesPlay)
6725               SendToProgram("go\n", cps);
6726             return;
6727       } else
6728         if (gameMode == PlayFromGameFile) {
6729             /* Stop reading this game file */
6730             gameMode = EditGame;
6731             ModeHighlight();
6732         }
6733         currentMove = --forwardMostMove;
6734         DisplayMove(currentMove-1); /* before DisplayMoveError */
6735         SwitchClocks();
6736         DisplayBothClocks();
6737         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6738                 parseList[currentMove], cps->which);
6739         DisplayMoveError(buf1);
6740         DrawPosition(FALSE, boards[currentMove]);
6741
6742         /* [HGM] illegal-move claim should forfeit game when Xboard */
6743         /* only passes fully legal moves                            */
6744         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6745             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6746                                 "False illegal-move claim", GE_XBOARD );
6747         }
6748         return;
6749     }
6750     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6751         /* Program has a broken "time" command that
6752            outputs a string not ending in newline.
6753            Don't use it. */
6754         cps->sendTime = 0;
6755     }
6756     
6757     /*
6758      * If chess program startup fails, exit with an error message.
6759      * Attempts to recover here are futile.
6760      */
6761     if ((StrStr(message, "unknown host") != NULL)
6762         || (StrStr(message, "No remote directory") != NULL)
6763         || (StrStr(message, "not found") != NULL)
6764         || (StrStr(message, "No such file") != NULL)
6765         || (StrStr(message, "can't alloc") != NULL)
6766         || (StrStr(message, "Permission denied") != NULL)) {
6767
6768         cps->maybeThinking = FALSE;
6769         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6770                 cps->which, cps->program, cps->host, message);
6771         RemoveInputSource(cps->isr);
6772         DisplayFatalError(buf1, 0, 1);
6773         return;
6774     }
6775     
6776     /* 
6777      * Look for hint output
6778      */
6779     if (sscanf(message, "Hint: %s", buf1) == 1) {
6780         if (cps == &first && hintRequested) {
6781             hintRequested = FALSE;
6782             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6783                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6784                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6785                                     PosFlags(forwardMostMove),
6786                                     fromY, fromX, toY, toX, promoChar, buf1);
6787                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6788                 DisplayInformation(buf2);
6789             } else {
6790                 /* Hint move could not be parsed!? */
6791               snprintf(buf2, sizeof(buf2),
6792                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6793                         buf1, cps->which);
6794                 DisplayError(buf2, 0);
6795             }
6796         } else {
6797             strcpy(lastHint, buf1);
6798         }
6799         return;
6800     }
6801
6802     /*
6803      * Ignore other messages if game is not in progress
6804      */
6805     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6806         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6807
6808     /*
6809      * look for win, lose, draw, or draw offer
6810      */
6811     if (strncmp(message, "1-0", 3) == 0) {
6812         char *p, *q, *r = "";
6813         p = strchr(message, '{');
6814         if (p) {
6815             q = strchr(p, '}');
6816             if (q) {
6817                 *q = NULLCHAR;
6818                 r = p + 1;
6819             }
6820         }
6821         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6822         return;
6823     } else if (strncmp(message, "0-1", 3) == 0) {
6824         char *p, *q, *r = "";
6825         p = strchr(message, '{');
6826         if (p) {
6827             q = strchr(p, '}');
6828             if (q) {
6829                 *q = NULLCHAR;
6830                 r = p + 1;
6831             }
6832         }
6833         /* Kludge for Arasan 4.1 bug */
6834         if (strcmp(r, "Black resigns") == 0) {
6835             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6836             return;
6837         }
6838         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6839         return;
6840     } else if (strncmp(message, "1/2", 3) == 0) {
6841         char *p, *q, *r = "";
6842         p = strchr(message, '{');
6843         if (p) {
6844             q = strchr(p, '}');
6845             if (q) {
6846                 *q = NULLCHAR;
6847                 r = p + 1;
6848             }
6849         }
6850             
6851         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6852         return;
6853
6854     } else if (strncmp(message, "White resign", 12) == 0) {
6855         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6856         return;
6857     } else if (strncmp(message, "Black resign", 12) == 0) {
6858         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6859         return;
6860     } else if (strncmp(message, "White matches", 13) == 0 ||
6861                strncmp(message, "Black matches", 13) == 0   ) {
6862         /* [HGM] ignore GNUShogi noises */
6863         return;
6864     } else if (strncmp(message, "White", 5) == 0 &&
6865                message[5] != '(' &&
6866                StrStr(message, "Black") == NULL) {
6867         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6868         return;
6869     } else if (strncmp(message, "Black", 5) == 0 &&
6870                message[5] != '(') {
6871         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6872         return;
6873     } else if (strcmp(message, "resign") == 0 ||
6874                strcmp(message, "computer resigns") == 0) {
6875         switch (gameMode) {
6876           case MachinePlaysBlack:
6877           case IcsPlayingBlack:
6878             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6879             break;
6880           case MachinePlaysWhite:
6881           case IcsPlayingWhite:
6882             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6883             break;
6884           case TwoMachinesPlay:
6885             if (cps->twoMachinesColor[0] == 'w')
6886               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6887             else
6888               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6889             break;
6890           default:
6891             /* can't happen */
6892             break;
6893         }
6894         return;
6895     } else if (strncmp(message, "opponent mates", 14) == 0) {
6896         switch (gameMode) {
6897           case MachinePlaysBlack:
6898           case IcsPlayingBlack:
6899             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6900             break;
6901           case MachinePlaysWhite:
6902           case IcsPlayingWhite:
6903             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6904             break;
6905           case TwoMachinesPlay:
6906             if (cps->twoMachinesColor[0] == 'w')
6907               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6908             else
6909               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6910             break;
6911           default:
6912             /* can't happen */
6913             break;
6914         }
6915         return;
6916     } else if (strncmp(message, "computer mates", 14) == 0) {
6917         switch (gameMode) {
6918           case MachinePlaysBlack:
6919           case IcsPlayingBlack:
6920             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6921             break;
6922           case MachinePlaysWhite:
6923           case IcsPlayingWhite:
6924             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6925             break;
6926           case TwoMachinesPlay:
6927             if (cps->twoMachinesColor[0] == 'w')
6928               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6929             else
6930               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6931             break;
6932           default:
6933             /* can't happen */
6934             break;
6935         }
6936         return;
6937     } else if (strncmp(message, "checkmate", 9) == 0) {
6938         if (WhiteOnMove(forwardMostMove)) {
6939             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6940         } else {
6941             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6942         }
6943         return;
6944     } else if (strstr(message, "Draw") != NULL ||
6945                strstr(message, "game is a draw") != NULL) {
6946         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6947         return;
6948     } else if (strstr(message, "offer") != NULL &&
6949                strstr(message, "draw") != NULL) {
6950 #if ZIPPY
6951         if (appData.zippyPlay && first.initDone) {
6952             /* Relay offer to ICS */
6953             SendToICS(ics_prefix);
6954             SendToICS("draw\n");
6955         }
6956 #endif
6957         cps->offeredDraw = 2; /* valid until this engine moves twice */
6958         if (gameMode == TwoMachinesPlay) {
6959             if (cps->other->offeredDraw) {
6960                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6961             /* [HGM] in two-machine mode we delay relaying draw offer      */
6962             /* until after we also have move, to see if it is really claim */
6963             }
6964         } else if (gameMode == MachinePlaysWhite ||
6965                    gameMode == MachinePlaysBlack) {
6966           if (userOfferedDraw) {
6967             DisplayInformation(_("Machine accepts your draw offer"));
6968             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6969           } else {
6970             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6971           }
6972         }
6973     }
6974
6975     
6976     /*
6977      * Look for thinking output
6978      */
6979     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6980           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6981                                 ) {
6982         int plylev, mvleft, mvtot, curscore, time;
6983         char mvname[MOVE_LEN];
6984         u64 nodes; // [DM]
6985         char plyext;
6986         int ignore = FALSE;
6987         int prefixHint = FALSE;
6988         mvname[0] = NULLCHAR;
6989
6990         switch (gameMode) {
6991           case MachinePlaysBlack:
6992           case IcsPlayingBlack:
6993             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6994             break;
6995           case MachinePlaysWhite:
6996           case IcsPlayingWhite:
6997             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6998             break;
6999           case AnalyzeMode:
7000           case AnalyzeFile:
7001             break;
7002           case IcsObserving: /* [DM] icsEngineAnalyze */
7003             if (!appData.icsEngineAnalyze) ignore = TRUE;
7004             break;
7005           case TwoMachinesPlay:
7006             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7007                 ignore = TRUE;
7008             }
7009             break;
7010           default:
7011             ignore = TRUE;
7012             break;
7013         }
7014
7015         if (!ignore) {
7016             buf1[0] = NULLCHAR;
7017             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7018                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7019
7020                 if (plyext != ' ' && plyext != '\t') {
7021                     time *= 100;
7022                 }
7023
7024                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7025                 if( cps->scoreIsAbsolute && 
7026                     ( gameMode == MachinePlaysBlack ||
7027                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7028                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7029                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7030                      !WhiteOnMove(currentMove)
7031                     ) )
7032                 {
7033                     curscore = -curscore;
7034                 }
7035
7036
7037                 programStats.depth = plylev;
7038                 programStats.nodes = nodes;
7039                 programStats.time = time;
7040                 programStats.score = curscore;
7041                 programStats.got_only_move = 0;
7042
7043                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7044                         int ticklen;
7045
7046                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7047                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7048                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7049                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7050                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7051                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7052                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7053                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7054                 }
7055
7056                 /* Buffer overflow protection */
7057                 if (buf1[0] != NULLCHAR) {
7058                     if (strlen(buf1) >= sizeof(programStats.movelist)
7059                         && appData.debugMode) {
7060                         fprintf(debugFP,
7061                                 "PV is too long; using the first %u bytes.\n",
7062                                 (unsigned) sizeof(programStats.movelist) - 1);
7063                     }
7064
7065                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7066                 } else {
7067                     sprintf(programStats.movelist, " no PV\n");
7068                 }
7069
7070                 if (programStats.seen_stat) {
7071                     programStats.ok_to_send = 1;
7072                 }
7073
7074                 if (strchr(programStats.movelist, '(') != NULL) {
7075                     programStats.line_is_book = 1;
7076                     programStats.nr_moves = 0;
7077                     programStats.moves_left = 0;
7078                 } else {
7079                     programStats.line_is_book = 0;
7080                 }
7081
7082                 SendProgramStatsToFrontend( cps, &programStats );
7083
7084                 /* 
7085                     [AS] Protect the thinkOutput buffer from overflow... this
7086                     is only useful if buf1 hasn't overflowed first!
7087                 */
7088                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7089                         plylev, 
7090                         (gameMode == TwoMachinesPlay ?
7091                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7092                         ((double) curscore) / 100.0,
7093                         prefixHint ? lastHint : "",
7094                         prefixHint ? " " : "" );
7095
7096                 if( buf1[0] != NULLCHAR ) {
7097                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7098
7099                     if( strlen(buf1) > max_len ) {
7100                         if( appData.debugMode) {
7101                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7102                         }
7103                         buf1[max_len+1] = '\0';
7104                     }
7105
7106                     strcat( thinkOutput, buf1 );
7107                 }
7108
7109                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7110                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7111                     DisplayMove(currentMove - 1);
7112                 }
7113                 return;
7114
7115             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7116                 /* crafty (9.25+) says "(only move) <move>"
7117                  * if there is only 1 legal move
7118                  */
7119                 sscanf(p, "(only move) %s", buf1);
7120                 sprintf(thinkOutput, "%s (only move)", buf1);
7121                 sprintf(programStats.movelist, "%s (only move)", buf1);
7122                 programStats.depth = 1;
7123                 programStats.nr_moves = 1;
7124                 programStats.moves_left = 1;
7125                 programStats.nodes = 1;
7126                 programStats.time = 1;
7127                 programStats.got_only_move = 1;
7128
7129                 /* Not really, but we also use this member to
7130                    mean "line isn't going to change" (Crafty
7131                    isn't searching, so stats won't change) */
7132                 programStats.line_is_book = 1;
7133
7134                 SendProgramStatsToFrontend( cps, &programStats );
7135                 
7136                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7137                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7138                     DisplayMove(currentMove - 1);
7139                 }
7140                 return;
7141             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7142                               &time, &nodes, &plylev, &mvleft,
7143                               &mvtot, mvname) >= 5) {
7144                 /* The stat01: line is from Crafty (9.29+) in response
7145                    to the "." command */
7146                 programStats.seen_stat = 1;
7147                 cps->maybeThinking = TRUE;
7148
7149                 if (programStats.got_only_move || !appData.periodicUpdates)
7150                   return;
7151
7152                 programStats.depth = plylev;
7153                 programStats.time = time;
7154                 programStats.nodes = nodes;
7155                 programStats.moves_left = mvleft;
7156                 programStats.nr_moves = mvtot;
7157                 strcpy(programStats.move_name, mvname);
7158                 programStats.ok_to_send = 1;
7159                 programStats.movelist[0] = '\0';
7160
7161                 SendProgramStatsToFrontend( cps, &programStats );
7162
7163                 return;
7164
7165             } else if (strncmp(message,"++",2) == 0) {
7166                 /* Crafty 9.29+ outputs this */
7167                 programStats.got_fail = 2;
7168                 return;
7169
7170             } else if (strncmp(message,"--",2) == 0) {
7171                 /* Crafty 9.29+ outputs this */
7172                 programStats.got_fail = 1;
7173                 return;
7174
7175             } else if (thinkOutput[0] != NULLCHAR &&
7176                        strncmp(message, "    ", 4) == 0) {
7177                 unsigned message_len;
7178
7179                 p = message;
7180                 while (*p && *p == ' ') p++;
7181
7182                 message_len = strlen( p );
7183
7184                 /* [AS] Avoid buffer overflow */
7185                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7186                     strcat(thinkOutput, " ");
7187                     strcat(thinkOutput, p);
7188                 }
7189
7190                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7191                     strcat(programStats.movelist, " ");
7192                     strcat(programStats.movelist, p);
7193                 }
7194
7195                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7196                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7197                     DisplayMove(currentMove - 1);
7198                 }
7199                 return;
7200             }
7201         }
7202         else {
7203             buf1[0] = NULLCHAR;
7204
7205             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7206                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7207             {
7208                 ChessProgramStats cpstats;
7209
7210                 if (plyext != ' ' && plyext != '\t') {
7211                     time *= 100;
7212                 }
7213
7214                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7215                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7216                     curscore = -curscore;
7217                 }
7218
7219                 cpstats.depth = plylev;
7220                 cpstats.nodes = nodes;
7221                 cpstats.time = time;
7222                 cpstats.score = curscore;
7223                 cpstats.got_only_move = 0;
7224                 cpstats.movelist[0] = '\0';
7225
7226                 if (buf1[0] != NULLCHAR) {
7227                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7228                 }
7229
7230                 cpstats.ok_to_send = 0;
7231                 cpstats.line_is_book = 0;
7232                 cpstats.nr_moves = 0;
7233                 cpstats.moves_left = 0;
7234
7235                 SendProgramStatsToFrontend( cps, &cpstats );
7236             }
7237         }
7238     }
7239 }
7240
7241
7242 /* Parse a game score from the character string "game", and
7243    record it as the history of the current game.  The game
7244    score is NOT assumed to start from the standard position. 
7245    The display is not updated in any way.
7246    */
7247 void
7248 ParseGameHistory(game)
7249      char *game;
7250 {
7251     ChessMove moveType;
7252     int fromX, fromY, toX, toY, boardIndex;
7253     char promoChar;
7254     char *p, *q;
7255     char buf[MSG_SIZ];
7256
7257     if (appData.debugMode)
7258       fprintf(debugFP, "Parsing game history: %s\n", game);
7259
7260     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7261     gameInfo.site = StrSave(appData.icsHost);
7262     gameInfo.date = PGNDate();
7263     gameInfo.round = StrSave("-");
7264
7265     /* Parse out names of players */
7266     while (*game == ' ') game++;
7267     p = buf;
7268     while (*game != ' ') *p++ = *game++;
7269     *p = NULLCHAR;
7270     gameInfo.white = StrSave(buf);
7271     while (*game == ' ') game++;
7272     p = buf;
7273     while (*game != ' ' && *game != '\n') *p++ = *game++;
7274     *p = NULLCHAR;
7275     gameInfo.black = StrSave(buf);
7276
7277     /* Parse moves */
7278     boardIndex = blackPlaysFirst ? 1 : 0;
7279     yynewstr(game);
7280     for (;;) {
7281         yyboardindex = boardIndex;
7282         moveType = (ChessMove) yylex();
7283         switch (moveType) {
7284           case IllegalMove:             /* maybe suicide chess, etc. */
7285   if (appData.debugMode) {
7286     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7287     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7288     setbuf(debugFP, NULL);
7289   }
7290           case WhitePromotionChancellor:
7291           case BlackPromotionChancellor:
7292           case WhitePromotionArchbishop:
7293           case BlackPromotionArchbishop:
7294           case WhitePromotionQueen:
7295           case BlackPromotionQueen:
7296           case WhitePromotionRook:
7297           case BlackPromotionRook:
7298           case WhitePromotionBishop:
7299           case BlackPromotionBishop:
7300           case WhitePromotionKnight:
7301           case BlackPromotionKnight:
7302           case WhitePromotionKing:
7303           case BlackPromotionKing:
7304           case NormalMove:
7305           case WhiteCapturesEnPassant:
7306           case BlackCapturesEnPassant:
7307           case WhiteKingSideCastle:
7308           case WhiteQueenSideCastle:
7309           case BlackKingSideCastle:
7310           case BlackQueenSideCastle:
7311           case WhiteKingSideCastleWild:
7312           case WhiteQueenSideCastleWild:
7313           case BlackKingSideCastleWild:
7314           case BlackQueenSideCastleWild:
7315           /* PUSH Fabien */
7316           case WhiteHSideCastleFR:
7317           case WhiteASideCastleFR:
7318           case BlackHSideCastleFR:
7319           case BlackASideCastleFR:
7320           /* POP Fabien */
7321             fromX = currentMoveString[0] - AAA;
7322             fromY = currentMoveString[1] - ONE;
7323             toX = currentMoveString[2] - AAA;
7324             toY = currentMoveString[3] - ONE;
7325             promoChar = currentMoveString[4];
7326             break;
7327           case WhiteDrop:
7328           case BlackDrop:
7329             fromX = moveType == WhiteDrop ?
7330               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7331             (int) CharToPiece(ToLower(currentMoveString[0]));
7332             fromY = DROP_RANK;
7333             toX = currentMoveString[2] - AAA;
7334             toY = currentMoveString[3] - ONE;
7335             promoChar = NULLCHAR;
7336             break;
7337           case AmbiguousMove:
7338             /* bug? */
7339             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7340   if (appData.debugMode) {
7341     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7342     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7343     setbuf(debugFP, NULL);
7344   }
7345             DisplayError(buf, 0);
7346             return;
7347           case ImpossibleMove:
7348             /* bug? */
7349             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7350   if (appData.debugMode) {
7351     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7352     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7353     setbuf(debugFP, NULL);
7354   }
7355             DisplayError(buf, 0);
7356             return;
7357           case (ChessMove) 0:   /* end of file */
7358             if (boardIndex < backwardMostMove) {
7359                 /* Oops, gap.  How did that happen? */
7360                 DisplayError(_("Gap in move list"), 0);
7361                 return;
7362             }
7363             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7364             if (boardIndex > forwardMostMove) {
7365                 forwardMostMove = boardIndex;
7366             }
7367             return;
7368           case ElapsedTime:
7369             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7370                 strcat(parseList[boardIndex-1], " ");
7371                 strcat(parseList[boardIndex-1], yy_text);
7372             }
7373             continue;
7374           case Comment:
7375           case PGNTag:
7376           case NAG:
7377           default:
7378             /* ignore */
7379             continue;
7380           case WhiteWins:
7381           case BlackWins:
7382           case GameIsDrawn:
7383           case GameUnfinished:
7384             if (gameMode == IcsExamining) {
7385                 if (boardIndex < backwardMostMove) {
7386                     /* Oops, gap.  How did that happen? */
7387                     return;
7388                 }
7389                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7390                 return;
7391             }
7392             gameInfo.result = moveType;
7393             p = strchr(yy_text, '{');
7394             if (p == NULL) p = strchr(yy_text, '(');
7395             if (p == NULL) {
7396                 p = yy_text;
7397                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7398             } else {
7399                 q = strchr(p, *p == '{' ? '}' : ')');
7400                 if (q != NULL) *q = NULLCHAR;
7401                 p++;
7402             }
7403             gameInfo.resultDetails = StrSave(p);
7404             continue;
7405         }
7406         if (boardIndex >= forwardMostMove &&
7407             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7408             backwardMostMove = blackPlaysFirst ? 1 : 0;
7409             return;
7410         }
7411         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7412                                  fromY, fromX, toY, toX, promoChar,
7413                                  parseList[boardIndex]);
7414         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7415         /* currentMoveString is set as a side-effect of yylex */
7416         strcpy(moveList[boardIndex], currentMoveString);
7417         strcat(moveList[boardIndex], "\n");
7418         boardIndex++;
7419         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7420         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7421           case MT_NONE:
7422           case MT_STALEMATE:
7423           default:
7424             break;
7425           case MT_CHECK:
7426             if(gameInfo.variant != VariantShogi)
7427                 strcat(parseList[boardIndex - 1], "+");
7428             break;
7429           case MT_CHECKMATE:
7430           case MT_STAINMATE:
7431             strcat(parseList[boardIndex - 1], "#");
7432             break;
7433         }
7434     }
7435 }
7436
7437
7438 /* Apply a move to the given board  */
7439 void
7440 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7441      int fromX, fromY, toX, toY;
7442      int promoChar;
7443      Board board;
7444 {
7445   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7446
7447     /* [HGM] compute & store e.p. status and castling rights for new position */
7448     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7449     { int i;
7450
7451       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7452       oldEP = (signed char)board[EP_STATUS];
7453       board[EP_STATUS] = EP_NONE;
7454
7455       if( board[toY][toX] != EmptySquare ) 
7456            board[EP_STATUS] = EP_CAPTURE;  
7457
7458       if( board[fromY][fromX] == WhitePawn ) {
7459            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7460                board[EP_STATUS] = EP_PAWN_MOVE;
7461            if( toY-fromY==2) {
7462                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7463                         gameInfo.variant != VariantBerolina || toX < fromX)
7464                       board[EP_STATUS] = toX | berolina;
7465                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7466                         gameInfo.variant != VariantBerolina || toX > fromX) 
7467                       board[EP_STATUS] = toX;
7468            }
7469       } else 
7470       if( board[fromY][fromX] == BlackPawn ) {
7471            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7472                board[EP_STATUS] = EP_PAWN_MOVE; 
7473            if( toY-fromY== -2) {
7474                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7475                         gameInfo.variant != VariantBerolina || toX < fromX)
7476                       board[EP_STATUS] = toX | berolina;
7477                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7478                         gameInfo.variant != VariantBerolina || toX > fromX) 
7479                       board[EP_STATUS] = toX;
7480            }
7481        }
7482
7483        for(i=0; i<nrCastlingRights; i++) {
7484            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7485               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7486              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7487        }
7488
7489     }
7490
7491   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7492   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7493        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7494          
7495   if (fromX == toX && fromY == toY) return;
7496
7497   if (fromY == DROP_RANK) {
7498         /* must be first */
7499         piece = board[toY][toX] = (ChessSquare) fromX;
7500   } else {
7501      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7502      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7503      if(gameInfo.variant == VariantKnightmate)
7504          king += (int) WhiteUnicorn - (int) WhiteKing;
7505
7506     /* Code added by Tord: */
7507     /* FRC castling assumed when king captures friendly rook. */
7508     if (board[fromY][fromX] == WhiteKing &&
7509              board[toY][toX] == WhiteRook) {
7510       board[fromY][fromX] = EmptySquare;
7511       board[toY][toX] = EmptySquare;
7512       if(toX > fromX) {
7513         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7514       } else {
7515         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7516       }
7517     } else if (board[fromY][fromX] == BlackKing &&
7518                board[toY][toX] == BlackRook) {
7519       board[fromY][fromX] = EmptySquare;
7520       board[toY][toX] = EmptySquare;
7521       if(toX > fromX) {
7522         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7523       } else {
7524         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7525       }
7526     /* End of code added by Tord */
7527
7528     } else if (board[fromY][fromX] == king
7529         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7530         && toY == fromY && toX > fromX+1) {
7531         board[fromY][fromX] = EmptySquare;
7532         board[toY][toX] = king;
7533         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7534         board[fromY][BOARD_RGHT-1] = EmptySquare;
7535     } else if (board[fromY][fromX] == king
7536         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7537                && toY == fromY && toX < fromX-1) {
7538         board[fromY][fromX] = EmptySquare;
7539         board[toY][toX] = king;
7540         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7541         board[fromY][BOARD_LEFT] = EmptySquare;
7542     } else if (board[fromY][fromX] == WhitePawn
7543                && toY == BOARD_HEIGHT-1
7544                && gameInfo.variant != VariantXiangqi
7545                ) {
7546         /* white pawn promotion */
7547         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7548         if (board[toY][toX] == EmptySquare) {
7549             board[toY][toX] = WhiteQueen;
7550         }
7551         if(gameInfo.variant==VariantBughouse ||
7552            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7553             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7554         board[fromY][fromX] = EmptySquare;
7555     } else if ((fromY == BOARD_HEIGHT-4)
7556                && (toX != fromX)
7557                && gameInfo.variant != VariantXiangqi
7558                && gameInfo.variant != VariantBerolina
7559                && (board[fromY][fromX] == WhitePawn)
7560                && (board[toY][toX] == EmptySquare)) {
7561         board[fromY][fromX] = EmptySquare;
7562         board[toY][toX] = WhitePawn;
7563         captured = board[toY - 1][toX];
7564         board[toY - 1][toX] = EmptySquare;
7565     } else if ((fromY == BOARD_HEIGHT-4)
7566                && (toX == fromX)
7567                && gameInfo.variant == VariantBerolina
7568                && (board[fromY][fromX] == WhitePawn)
7569                && (board[toY][toX] == EmptySquare)) {
7570         board[fromY][fromX] = EmptySquare;
7571         board[toY][toX] = WhitePawn;
7572         if(oldEP & EP_BEROLIN_A) {
7573                 captured = board[fromY][fromX-1];
7574                 board[fromY][fromX-1] = EmptySquare;
7575         }else{  captured = board[fromY][fromX+1];
7576                 board[fromY][fromX+1] = EmptySquare;
7577         }
7578     } else if (board[fromY][fromX] == king
7579         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7580                && toY == fromY && toX > fromX+1) {
7581         board[fromY][fromX] = EmptySquare;
7582         board[toY][toX] = king;
7583         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7584         board[fromY][BOARD_RGHT-1] = EmptySquare;
7585     } else if (board[fromY][fromX] == king
7586         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7587                && toY == fromY && toX < fromX-1) {
7588         board[fromY][fromX] = EmptySquare;
7589         board[toY][toX] = king;
7590         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7591         board[fromY][BOARD_LEFT] = EmptySquare;
7592     } else if (fromY == 7 && fromX == 3
7593                && board[fromY][fromX] == BlackKing
7594                && toY == 7 && toX == 5) {
7595         board[fromY][fromX] = EmptySquare;
7596         board[toY][toX] = BlackKing;
7597         board[fromY][7] = EmptySquare;
7598         board[toY][4] = BlackRook;
7599     } else if (fromY == 7 && fromX == 3
7600                && board[fromY][fromX] == BlackKing
7601                && toY == 7 && toX == 1) {
7602         board[fromY][fromX] = EmptySquare;
7603         board[toY][toX] = BlackKing;
7604         board[fromY][0] = EmptySquare;
7605         board[toY][2] = BlackRook;
7606     } else if (board[fromY][fromX] == BlackPawn
7607                && toY == 0
7608                && gameInfo.variant != VariantXiangqi
7609                ) {
7610         /* black pawn promotion */
7611         board[0][toX] = CharToPiece(ToLower(promoChar));
7612         if (board[0][toX] == EmptySquare) {
7613             board[0][toX] = BlackQueen;
7614         }
7615         if(gameInfo.variant==VariantBughouse ||
7616            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7617             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7618         board[fromY][fromX] = EmptySquare;
7619     } else if ((fromY == 3)
7620                && (toX != fromX)
7621                && gameInfo.variant != VariantXiangqi
7622                && gameInfo.variant != VariantBerolina
7623                && (board[fromY][fromX] == BlackPawn)
7624                && (board[toY][toX] == EmptySquare)) {
7625         board[fromY][fromX] = EmptySquare;
7626         board[toY][toX] = BlackPawn;
7627         captured = board[toY + 1][toX];
7628         board[toY + 1][toX] = EmptySquare;
7629     } else if ((fromY == 3)
7630                && (toX == fromX)
7631                && gameInfo.variant == VariantBerolina
7632                && (board[fromY][fromX] == BlackPawn)
7633                && (board[toY][toX] == EmptySquare)) {
7634         board[fromY][fromX] = EmptySquare;
7635         board[toY][toX] = BlackPawn;
7636         if(oldEP & EP_BEROLIN_A) {
7637                 captured = board[fromY][fromX-1];
7638                 board[fromY][fromX-1] = EmptySquare;
7639         }else{  captured = board[fromY][fromX+1];
7640                 board[fromY][fromX+1] = EmptySquare;
7641         }
7642     } else {
7643         board[toY][toX] = board[fromY][fromX];
7644         board[fromY][fromX] = EmptySquare;
7645     }
7646
7647     /* [HGM] now we promote for Shogi, if needed */
7648     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7649         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7650   }
7651
7652     if (gameInfo.holdingsWidth != 0) {
7653
7654       /* !!A lot more code needs to be written to support holdings  */
7655       /* [HGM] OK, so I have written it. Holdings are stored in the */
7656       /* penultimate board files, so they are automaticlly stored   */
7657       /* in the game history.                                       */
7658       if (fromY == DROP_RANK) {
7659         /* Delete from holdings, by decreasing count */
7660         /* and erasing image if necessary            */
7661         p = (int) fromX;
7662         if(p < (int) BlackPawn) { /* white drop */
7663              p -= (int)WhitePawn;
7664                  p = PieceToNumber((ChessSquare)p);
7665              if(p >= gameInfo.holdingsSize) p = 0;
7666              if(--board[p][BOARD_WIDTH-2] <= 0)
7667                   board[p][BOARD_WIDTH-1] = EmptySquare;
7668              if((int)board[p][BOARD_WIDTH-2] < 0)
7669                         board[p][BOARD_WIDTH-2] = 0;
7670         } else {                  /* black drop */
7671              p -= (int)BlackPawn;
7672                  p = PieceToNumber((ChessSquare)p);
7673              if(p >= gameInfo.holdingsSize) p = 0;
7674              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7675                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7676              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7677                         board[BOARD_HEIGHT-1-p][1] = 0;
7678         }
7679       }
7680       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7681           && gameInfo.variant != VariantBughouse        ) {
7682         /* [HGM] holdings: Add to holdings, if holdings exist */
7683         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7684                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7685                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7686         }
7687         p = (int) captured;
7688         if (p >= (int) BlackPawn) {
7689           p -= (int)BlackPawn;
7690           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7691                   /* in Shogi restore piece to its original  first */
7692                   captured = (ChessSquare) (DEMOTED captured);
7693                   p = DEMOTED p;
7694           }
7695           p = PieceToNumber((ChessSquare)p);
7696           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7697           board[p][BOARD_WIDTH-2]++;
7698           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7699         } else {
7700           p -= (int)WhitePawn;
7701           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7702                   captured = (ChessSquare) (DEMOTED captured);
7703                   p = DEMOTED p;
7704           }
7705           p = PieceToNumber((ChessSquare)p);
7706           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7707           board[BOARD_HEIGHT-1-p][1]++;
7708           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7709         }
7710       }
7711     } else if (gameInfo.variant == VariantAtomic) {
7712       if (captured != EmptySquare) {
7713         int y, x;
7714         for (y = toY-1; y <= toY+1; y++) {
7715           for (x = toX-1; x <= toX+1; x++) {
7716             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7717                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7718               board[y][x] = EmptySquare;
7719             }
7720           }
7721         }
7722         board[toY][toX] = EmptySquare;
7723       }
7724     }
7725     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7726         /* [HGM] Shogi promotions */
7727         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7728     }
7729
7730     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7731                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7732         // [HGM] superchess: take promotion piece out of holdings
7733         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7734         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7735             if(!--board[k][BOARD_WIDTH-2])
7736                 board[k][BOARD_WIDTH-1] = EmptySquare;
7737         } else {
7738             if(!--board[BOARD_HEIGHT-1-k][1])
7739                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7740         }
7741     }
7742
7743 }
7744
7745 /* Updates forwardMostMove */
7746 void
7747 MakeMove(fromX, fromY, toX, toY, promoChar)
7748      int fromX, fromY, toX, toY;
7749      int promoChar;
7750 {
7751 //    forwardMostMove++; // [HGM] bare: moved downstream
7752
7753     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7754         int timeLeft; static int lastLoadFlag=0; int king, piece;
7755         piece = boards[forwardMostMove][fromY][fromX];
7756         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7757         if(gameInfo.variant == VariantKnightmate)
7758             king += (int) WhiteUnicorn - (int) WhiteKing;
7759         if(forwardMostMove == 0) {
7760             if(blackPlaysFirst) 
7761                 fprintf(serverMoves, "%s;", second.tidy);
7762             fprintf(serverMoves, "%s;", first.tidy);
7763             if(!blackPlaysFirst) 
7764                 fprintf(serverMoves, "%s;", second.tidy);
7765         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7766         lastLoadFlag = loadFlag;
7767         // print base move
7768         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7769         // print castling suffix
7770         if( toY == fromY && piece == king ) {
7771             if(toX-fromX > 1)
7772                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7773             if(fromX-toX >1)
7774                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7775         }
7776         // e.p. suffix
7777         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7778              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7779              boards[forwardMostMove][toY][toX] == EmptySquare
7780              && fromX != toX )
7781                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7782         // promotion suffix
7783         if(promoChar != NULLCHAR)
7784                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7785         if(!loadFlag) {
7786             fprintf(serverMoves, "/%d/%d",
7787                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7788             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7789             else                      timeLeft = blackTimeRemaining/1000;
7790             fprintf(serverMoves, "/%d", timeLeft);
7791         }
7792         fflush(serverMoves);
7793     }
7794
7795     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7796       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7797                         0, 1);
7798       return;
7799     }
7800     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7801     if (commentList[forwardMostMove+1] != NULL) {
7802         free(commentList[forwardMostMove+1]);
7803         commentList[forwardMostMove+1] = NULL;
7804     }
7805     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7806     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7807     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7808     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7809     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7810     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7811     gameInfo.result = GameUnfinished;
7812     if (gameInfo.resultDetails != NULL) {
7813         free(gameInfo.resultDetails);
7814         gameInfo.resultDetails = NULL;
7815     }
7816     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7817                               moveList[forwardMostMove - 1]);
7818     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7819                              PosFlags(forwardMostMove - 1),
7820                              fromY, fromX, toY, toX, promoChar,
7821                              parseList[forwardMostMove - 1]);
7822     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7823       case MT_NONE:
7824       case MT_STALEMATE:
7825       default:
7826         break;
7827       case MT_CHECK:
7828         if(gameInfo.variant != VariantShogi)
7829             strcat(parseList[forwardMostMove - 1], "+");
7830         break;
7831       case MT_CHECKMATE:
7832       case MT_STAINMATE:
7833         strcat(parseList[forwardMostMove - 1], "#");
7834         break;
7835     }
7836     if (appData.debugMode) {
7837         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7838     }
7839
7840 }
7841
7842 /* Updates currentMove if not pausing */
7843 void
7844 ShowMove(fromX, fromY, toX, toY)
7845 {
7846     int instant = (gameMode == PlayFromGameFile) ?
7847         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7848     if(appData.noGUI) return;
7849     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7850         if (!instant) {
7851             if (forwardMostMove == currentMove + 1) {
7852                 AnimateMove(boards[forwardMostMove - 1],
7853                             fromX, fromY, toX, toY);
7854             }
7855             if (appData.highlightLastMove) {
7856                 SetHighlights(fromX, fromY, toX, toY);
7857             }
7858         }
7859         currentMove = forwardMostMove;
7860     }
7861
7862     if (instant) return;
7863
7864     DisplayMove(currentMove - 1);
7865     DrawPosition(FALSE, boards[currentMove]);
7866     DisplayBothClocks();
7867     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7868 }
7869
7870 void SendEgtPath(ChessProgramState *cps)
7871 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7872         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7873
7874         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7875
7876         while(*p) {
7877             char c, *q = name+1, *r, *s;
7878
7879             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7880             while(*p && *p != ',') *q++ = *p++;
7881             *q++ = ':'; *q = 0;
7882             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7883                 strcmp(name, ",nalimov:") == 0 ) {
7884                 // take nalimov path from the menu-changeable option first, if it is defined
7885                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7886                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7887             } else
7888             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7889                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7890                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7891                 s = r = StrStr(s, ":") + 1; // beginning of path info
7892                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7893                 c = *r; *r = 0;             // temporarily null-terminate path info
7894                     *--q = 0;               // strip of trailig ':' from name
7895                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7896                 *r = c;
7897                 SendToProgram(buf,cps);     // send egtbpath command for this format
7898             }
7899             if(*p == ',') p++; // read away comma to position for next format name
7900         }
7901 }
7902
7903 void
7904 InitChessProgram(cps, setup)
7905      ChessProgramState *cps;
7906      int setup; /* [HGM] needed to setup FRC opening position */
7907 {
7908     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7909     if (appData.noChessProgram) return;
7910     hintRequested = FALSE;
7911     bookRequested = FALSE;
7912
7913     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7914     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7915     if(cps->memSize) { /* [HGM] memory */
7916         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7917         SendToProgram(buf, cps);
7918     }
7919     SendEgtPath(cps); /* [HGM] EGT */
7920     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7921         sprintf(buf, "cores %d\n", appData.smpCores);
7922         SendToProgram(buf, cps);
7923     }
7924
7925     SendToProgram(cps->initString, cps);
7926     if (gameInfo.variant != VariantNormal &&
7927         gameInfo.variant != VariantLoadable
7928         /* [HGM] also send variant if board size non-standard */
7929         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7930                                             ) {
7931       char *v = VariantName(gameInfo.variant);
7932       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7933         /* [HGM] in protocol 1 we have to assume all variants valid */
7934         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7935         DisplayFatalError(buf, 0, 1);
7936         return;
7937       }
7938
7939       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7940       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7941       if( gameInfo.variant == VariantXiangqi )
7942            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7943       if( gameInfo.variant == VariantShogi )
7944            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7945       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7946            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7947       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7948                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7949            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7950       if( gameInfo.variant == VariantCourier )
7951            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7952       if( gameInfo.variant == VariantSuper )
7953            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7954       if( gameInfo.variant == VariantGreat )
7955            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7956
7957       if(overruled) {
7958            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7959                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7960            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7961            if(StrStr(cps->variants, b) == NULL) { 
7962                // specific sized variant not known, check if general sizing allowed
7963                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7964                    if(StrStr(cps->variants, "boardsize") == NULL) {
7965                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7966                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7967                        DisplayFatalError(buf, 0, 1);
7968                        return;
7969                    }
7970                    /* [HGM] here we really should compare with the maximum supported board size */
7971                }
7972            }
7973       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7974       sprintf(buf, "variant %s\n", b);
7975       SendToProgram(buf, cps);
7976     }
7977     currentlyInitializedVariant = gameInfo.variant;
7978
7979     /* [HGM] send opening position in FRC to first engine */
7980     if(setup) {
7981           SendToProgram("force\n", cps);
7982           SendBoard(cps, 0);
7983           /* engine is now in force mode! Set flag to wake it up after first move. */
7984           setboardSpoiledMachineBlack = 1;
7985     }
7986
7987     if (cps->sendICS) {
7988       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7989       SendToProgram(buf, cps);
7990     }
7991     cps->maybeThinking = FALSE;
7992     cps->offeredDraw = 0;
7993     if (!appData.icsActive) {
7994         SendTimeControl(cps, movesPerSession, timeControl,
7995                         timeIncrement, appData.searchDepth,
7996                         searchTime);
7997     }
7998     if (appData.showThinking 
7999         // [HGM] thinking: four options require thinking output to be sent
8000         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8001                                 ) {
8002         SendToProgram("post\n", cps);
8003     }
8004     SendToProgram("hard\n", cps);
8005     if (!appData.ponderNextMove) {
8006         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8007            it without being sure what state we are in first.  "hard"
8008            is not a toggle, so that one is OK.
8009          */
8010         SendToProgram("easy\n", cps);
8011     }
8012     if (cps->usePing) {
8013       sprintf(buf, "ping %d\n", ++cps->lastPing);
8014       SendToProgram(buf, cps);
8015     }
8016     cps->initDone = TRUE;
8017 }   
8018
8019
8020 void
8021 StartChessProgram(cps)
8022      ChessProgramState *cps;
8023 {
8024     char buf[MSG_SIZ];
8025     int err;
8026
8027     if (appData.noChessProgram) return;
8028     cps->initDone = FALSE;
8029
8030     if (strcmp(cps->host, "localhost") == 0) {
8031         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8032     } else if (*appData.remoteShell == NULLCHAR) {
8033         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8034     } else {
8035         if (*appData.remoteUser == NULLCHAR) {
8036           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8037                     cps->program);
8038         } else {
8039           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8040                     cps->host, appData.remoteUser, cps->program);
8041         }
8042         err = StartChildProcess(buf, "", &cps->pr);
8043     }
8044     
8045     if (err != 0) {
8046         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8047         DisplayFatalError(buf, err, 1);
8048         cps->pr = NoProc;
8049         cps->isr = NULL;
8050         return;
8051     }
8052     
8053     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8054     if (cps->protocolVersion > 1) {
8055       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8056       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8057       cps->comboCnt = 0;  //                and values of combo boxes
8058       SendToProgram(buf, cps);
8059     } else {
8060       SendToProgram("xboard\n", cps);
8061     }
8062 }
8063
8064
8065 void
8066 TwoMachinesEventIfReady P((void))
8067 {
8068   if (first.lastPing != first.lastPong) {
8069     DisplayMessage("", _("Waiting for first chess program"));
8070     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8071     return;
8072   }
8073   if (second.lastPing != second.lastPong) {
8074     DisplayMessage("", _("Waiting for second chess program"));
8075     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8076     return;
8077   }
8078   ThawUI();
8079   TwoMachinesEvent();
8080 }
8081
8082 void
8083 NextMatchGame P((void))
8084 {
8085     int index; /* [HGM] autoinc: step load index during match */
8086     Reset(FALSE, TRUE);
8087     if (*appData.loadGameFile != NULLCHAR) {
8088         index = appData.loadGameIndex;
8089         if(index < 0) { // [HGM] autoinc
8090             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8091             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8092         } 
8093         LoadGameFromFile(appData.loadGameFile,
8094                          index,
8095                          appData.loadGameFile, FALSE);
8096     } else if (*appData.loadPositionFile != NULLCHAR) {
8097         index = appData.loadPositionIndex;
8098         if(index < 0) { // [HGM] autoinc
8099             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8100             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8101         } 
8102         LoadPositionFromFile(appData.loadPositionFile,
8103                              index,
8104                              appData.loadPositionFile);
8105     }
8106     TwoMachinesEventIfReady();
8107 }
8108
8109 void UserAdjudicationEvent( int result )
8110 {
8111     ChessMove gameResult = GameIsDrawn;
8112
8113     if( result > 0 ) {
8114         gameResult = WhiteWins;
8115     }
8116     else if( result < 0 ) {
8117         gameResult = BlackWins;
8118     }
8119
8120     if( gameMode == TwoMachinesPlay ) {
8121         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8122     }
8123 }
8124
8125
8126 // [HGM] save: calculate checksum of game to make games easily identifiable
8127 int StringCheckSum(char *s)
8128 {
8129         int i = 0;
8130         if(s==NULL) return 0;
8131         while(*s) i = i*259 + *s++;
8132         return i;
8133 }
8134
8135 int GameCheckSum()
8136 {
8137         int i, sum=0;
8138         for(i=backwardMostMove; i<forwardMostMove; i++) {
8139                 sum += pvInfoList[i].depth;
8140                 sum += StringCheckSum(parseList[i]);
8141                 sum += StringCheckSum(commentList[i]);
8142                 sum *= 261;
8143         }
8144         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8145         return sum + StringCheckSum(commentList[i]);
8146 } // end of save patch
8147
8148 void
8149 GameEnds(result, resultDetails, whosays)
8150      ChessMove result;
8151      char *resultDetails;
8152      int whosays;
8153 {
8154     GameMode nextGameMode;
8155     int isIcsGame;
8156     char buf[MSG_SIZ];
8157
8158     if(endingGame) return; /* [HGM] crash: forbid recursion */
8159     endingGame = 1;
8160
8161     if (appData.debugMode) {
8162       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8163               result, resultDetails ? resultDetails : "(null)", whosays);
8164     }
8165
8166     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8167         /* If we are playing on ICS, the server decides when the
8168            game is over, but the engine can offer to draw, claim 
8169            a draw, or resign. 
8170          */
8171 #if ZIPPY
8172         if (appData.zippyPlay && first.initDone) {
8173             if (result == GameIsDrawn) {
8174                 /* In case draw still needs to be claimed */
8175                 SendToICS(ics_prefix);
8176                 SendToICS("draw\n");
8177             } else if (StrCaseStr(resultDetails, "resign")) {
8178                 SendToICS(ics_prefix);
8179                 SendToICS("resign\n");
8180             }
8181         }
8182 #endif
8183         endingGame = 0; /* [HGM] crash */
8184         return;
8185     }
8186
8187     /* If we're loading the game from a file, stop */
8188     if (whosays == GE_FILE) {
8189       (void) StopLoadGameTimer();
8190       gameFileFP = NULL;
8191     }
8192
8193     /* Cancel draw offers */
8194     first.offeredDraw = second.offeredDraw = 0;
8195
8196     /* If this is an ICS game, only ICS can really say it's done;
8197        if not, anyone can. */
8198     isIcsGame = (gameMode == IcsPlayingWhite || 
8199                  gameMode == IcsPlayingBlack || 
8200                  gameMode == IcsObserving    || 
8201                  gameMode == IcsExamining);
8202
8203     if (!isIcsGame || whosays == GE_ICS) {
8204         /* OK -- not an ICS game, or ICS said it was done */
8205         StopClocks();
8206         if (!isIcsGame && !appData.noChessProgram) 
8207           SetUserThinkingEnables();
8208     
8209         /* [HGM] if a machine claims the game end we verify this claim */
8210         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8211             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8212                 char claimer;
8213                 ChessMove trueResult = (ChessMove) -1;
8214
8215                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8216                                             first.twoMachinesColor[0] :
8217                                             second.twoMachinesColor[0] ;
8218
8219                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8220                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8221                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8222                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8223                 } else
8224                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8225                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8226                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8227                 } else
8228                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8229                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8230                 }
8231
8232                 // now verify win claims, but not in drop games, as we don't understand those yet
8233                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8234                                                  || gameInfo.variant == VariantGreat) &&
8235                     (result == WhiteWins && claimer == 'w' ||
8236                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8237                       if (appData.debugMode) {
8238                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8239                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8240                       }
8241                       if(result != trueResult) {
8242                               sprintf(buf, "False win claim: '%s'", resultDetails);
8243                               result = claimer == 'w' ? BlackWins : WhiteWins;
8244                               resultDetails = buf;
8245                       }
8246                 } else
8247                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8248                     && (forwardMostMove <= backwardMostMove ||
8249                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8250                         (claimer=='b')==(forwardMostMove&1))
8251                                                                                   ) {
8252                       /* [HGM] verify: draws that were not flagged are false claims */
8253                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8254                       result = claimer == 'w' ? BlackWins : WhiteWins;
8255                       resultDetails = buf;
8256                 }
8257                 /* (Claiming a loss is accepted no questions asked!) */
8258             }
8259             /* [HGM] bare: don't allow bare King to win */
8260             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8261                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8262                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8263                && result != GameIsDrawn)
8264             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8265                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8266                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8267                         if(p >= 0 && p <= (int)WhiteKing) k++;
8268                 }
8269                 if (appData.debugMode) {
8270                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8271                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8272                 }
8273                 if(k <= 1) {
8274                         result = GameIsDrawn;
8275                         sprintf(buf, "%s but bare king", resultDetails);
8276                         resultDetails = buf;
8277                 }
8278             }
8279         }
8280
8281
8282         if(serverMoves != NULL && !loadFlag) { char c = '=';
8283             if(result==WhiteWins) c = '+';
8284             if(result==BlackWins) c = '-';
8285             if(resultDetails != NULL)
8286                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8287         }
8288         if (resultDetails != NULL) {
8289             gameInfo.result = result;
8290             gameInfo.resultDetails = StrSave(resultDetails);
8291
8292             /* display last move only if game was not loaded from file */
8293             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8294                 DisplayMove(currentMove - 1);
8295     
8296             if (forwardMostMove != 0) {
8297                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8298                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8299                                                                 ) {
8300                     if (*appData.saveGameFile != NULLCHAR) {
8301                         SaveGameToFile(appData.saveGameFile, TRUE);
8302                     } else if (appData.autoSaveGames) {
8303                         AutoSaveGame();
8304                     }
8305                     if (*appData.savePositionFile != NULLCHAR) {
8306                         SavePositionToFile(appData.savePositionFile);
8307                     }
8308                 }
8309             }
8310
8311             /* Tell program how game ended in case it is learning */
8312             /* [HGM] Moved this to after saving the PGN, just in case */
8313             /* engine died and we got here through time loss. In that */
8314             /* case we will get a fatal error writing the pipe, which */
8315             /* would otherwise lose us the PGN.                       */
8316             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8317             /* output during GameEnds should never be fatal anymore   */
8318             if (gameMode == MachinePlaysWhite ||
8319                 gameMode == MachinePlaysBlack ||
8320                 gameMode == TwoMachinesPlay ||
8321                 gameMode == IcsPlayingWhite ||
8322                 gameMode == IcsPlayingBlack ||
8323                 gameMode == BeginningOfGame) {
8324                 char buf[MSG_SIZ];
8325                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8326                         resultDetails);
8327                 if (first.pr != NoProc) {
8328                     SendToProgram(buf, &first);
8329                 }
8330                 if (second.pr != NoProc &&
8331                     gameMode == TwoMachinesPlay) {
8332                     SendToProgram(buf, &second);
8333                 }
8334             }
8335         }
8336
8337         if (appData.icsActive) {
8338             if (appData.quietPlay &&
8339                 (gameMode == IcsPlayingWhite ||
8340                  gameMode == IcsPlayingBlack)) {
8341                 SendToICS(ics_prefix);
8342                 SendToICS("set shout 1\n");
8343             }
8344             nextGameMode = IcsIdle;
8345             ics_user_moved = FALSE;
8346             /* clean up premove.  It's ugly when the game has ended and the
8347              * premove highlights are still on the board.
8348              */
8349             if (gotPremove) {
8350               gotPremove = FALSE;
8351               ClearPremoveHighlights();
8352               DrawPosition(FALSE, boards[currentMove]);
8353             }
8354             if (whosays == GE_ICS) {
8355                 switch (result) {
8356                 case WhiteWins:
8357                     if (gameMode == IcsPlayingWhite)
8358                         PlayIcsWinSound();
8359                     else if(gameMode == IcsPlayingBlack)
8360                         PlayIcsLossSound();
8361                     break;
8362                 case BlackWins:
8363                     if (gameMode == IcsPlayingBlack)
8364                         PlayIcsWinSound();
8365                     else if(gameMode == IcsPlayingWhite)
8366                         PlayIcsLossSound();
8367                     break;
8368                 case GameIsDrawn:
8369                     PlayIcsDrawSound();
8370                     break;
8371                 default:
8372                     PlayIcsUnfinishedSound();
8373                 }
8374             }
8375         } else if (gameMode == EditGame ||
8376                    gameMode == PlayFromGameFile || 
8377                    gameMode == AnalyzeMode || 
8378                    gameMode == AnalyzeFile) {
8379             nextGameMode = gameMode;
8380         } else {
8381             nextGameMode = EndOfGame;
8382         }
8383         pausing = FALSE;
8384         ModeHighlight();
8385     } else {
8386         nextGameMode = gameMode;
8387     }
8388
8389     if (appData.noChessProgram) {
8390         gameMode = nextGameMode;
8391         ModeHighlight();
8392         endingGame = 0; /* [HGM] crash */
8393         return;
8394     }
8395
8396     if (first.reuse) {
8397         /* Put first chess program into idle state */
8398         if (first.pr != NoProc &&
8399             (gameMode == MachinePlaysWhite ||
8400              gameMode == MachinePlaysBlack ||
8401              gameMode == TwoMachinesPlay ||
8402              gameMode == IcsPlayingWhite ||
8403              gameMode == IcsPlayingBlack ||
8404              gameMode == BeginningOfGame)) {
8405             SendToProgram("force\n", &first);
8406             if (first.usePing) {
8407               char buf[MSG_SIZ];
8408               sprintf(buf, "ping %d\n", ++first.lastPing);
8409               SendToProgram(buf, &first);
8410             }
8411         }
8412     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8413         /* Kill off first chess program */
8414         if (first.isr != NULL)
8415           RemoveInputSource(first.isr);
8416         first.isr = NULL;
8417     
8418         if (first.pr != NoProc) {
8419             ExitAnalyzeMode();
8420             DoSleep( appData.delayBeforeQuit );
8421             SendToProgram("quit\n", &first);
8422             DoSleep( appData.delayAfterQuit );
8423             DestroyChildProcess(first.pr, first.useSigterm);
8424         }
8425         first.pr = NoProc;
8426     }
8427     if (second.reuse) {
8428         /* Put second chess program into idle state */
8429         if (second.pr != NoProc &&
8430             gameMode == TwoMachinesPlay) {
8431             SendToProgram("force\n", &second);
8432             if (second.usePing) {
8433               char buf[MSG_SIZ];
8434               sprintf(buf, "ping %d\n", ++second.lastPing);
8435               SendToProgram(buf, &second);
8436             }
8437         }
8438     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8439         /* Kill off second chess program */
8440         if (second.isr != NULL)
8441           RemoveInputSource(second.isr);
8442         second.isr = NULL;
8443     
8444         if (second.pr != NoProc) {
8445             DoSleep( appData.delayBeforeQuit );
8446             SendToProgram("quit\n", &second);
8447             DoSleep( appData.delayAfterQuit );
8448             DestroyChildProcess(second.pr, second.useSigterm);
8449         }
8450         second.pr = NoProc;
8451     }
8452
8453     if (matchMode && gameMode == TwoMachinesPlay) {
8454         switch (result) {
8455         case WhiteWins:
8456           if (first.twoMachinesColor[0] == 'w') {
8457             first.matchWins++;
8458           } else {
8459             second.matchWins++;
8460           }
8461           break;
8462         case BlackWins:
8463           if (first.twoMachinesColor[0] == 'b') {
8464             first.matchWins++;
8465           } else {
8466             second.matchWins++;
8467           }
8468           break;
8469         default:
8470           break;
8471         }
8472         if (matchGame < appData.matchGames) {
8473             char *tmp;
8474             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8475                 tmp = first.twoMachinesColor;
8476                 first.twoMachinesColor = second.twoMachinesColor;
8477                 second.twoMachinesColor = tmp;
8478             }
8479             gameMode = nextGameMode;
8480             matchGame++;
8481             if(appData.matchPause>10000 || appData.matchPause<10)
8482                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8483             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8484             endingGame = 0; /* [HGM] crash */
8485             return;
8486         } else {
8487             char buf[MSG_SIZ];
8488             gameMode = nextGameMode;
8489             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8490                     first.tidy, second.tidy,
8491                     first.matchWins, second.matchWins,
8492                     appData.matchGames - (first.matchWins + second.matchWins));
8493             DisplayFatalError(buf, 0, 0);
8494         }
8495     }
8496     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8497         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8498       ExitAnalyzeMode();
8499     gameMode = nextGameMode;
8500     ModeHighlight();
8501     endingGame = 0;  /* [HGM] crash */
8502 }
8503
8504 /* Assumes program was just initialized (initString sent).
8505    Leaves program in force mode. */
8506 void
8507 FeedMovesToProgram(cps, upto) 
8508      ChessProgramState *cps;
8509      int upto;
8510 {
8511     int i;
8512     
8513     if (appData.debugMode)
8514       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8515               startedFromSetupPosition ? "position and " : "",
8516               backwardMostMove, upto, cps->which);
8517     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8518         // [HGM] variantswitch: make engine aware of new variant
8519         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8520                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8521         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8522         SendToProgram(buf, cps);
8523         currentlyInitializedVariant = gameInfo.variant;
8524     }
8525     SendToProgram("force\n", cps);
8526     if (startedFromSetupPosition) {
8527         SendBoard(cps, backwardMostMove);
8528     if (appData.debugMode) {
8529         fprintf(debugFP, "feedMoves\n");
8530     }
8531     }
8532     for (i = backwardMostMove; i < upto; i++) {
8533         SendMoveToProgram(i, cps);
8534     }
8535 }
8536
8537
8538 void
8539 ResurrectChessProgram()
8540 {
8541      /* The chess program may have exited.
8542         If so, restart it and feed it all the moves made so far. */
8543
8544     if (appData.noChessProgram || first.pr != NoProc) return;
8545     
8546     StartChessProgram(&first);
8547     InitChessProgram(&first, FALSE);
8548     FeedMovesToProgram(&first, currentMove);
8549
8550     if (!first.sendTime) {
8551         /* can't tell gnuchess what its clock should read,
8552            so we bow to its notion. */
8553         ResetClocks();
8554         timeRemaining[0][currentMove] = whiteTimeRemaining;
8555         timeRemaining[1][currentMove] = blackTimeRemaining;
8556     }
8557
8558     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8559                 appData.icsEngineAnalyze) && first.analysisSupport) {
8560       SendToProgram("analyze\n", &first);
8561       first.analyzing = TRUE;
8562     }
8563 }
8564
8565 /*
8566  * Button procedures
8567  */
8568 void
8569 Reset(redraw, init)
8570      int redraw, init;
8571 {
8572     int i;
8573
8574     if (appData.debugMode) {
8575         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8576                 redraw, init, gameMode);
8577     }
8578     CleanupTail(); // [HGM] vari: delete any stored variations
8579     pausing = pauseExamInvalid = FALSE;
8580     startedFromSetupPosition = blackPlaysFirst = FALSE;
8581     firstMove = TRUE;
8582     whiteFlag = blackFlag = FALSE;
8583     userOfferedDraw = FALSE;
8584     hintRequested = bookRequested = FALSE;
8585     first.maybeThinking = FALSE;
8586     second.maybeThinking = FALSE;
8587     first.bookSuspend = FALSE; // [HGM] book
8588     second.bookSuspend = FALSE;
8589     thinkOutput[0] = NULLCHAR;
8590     lastHint[0] = NULLCHAR;
8591     ClearGameInfo(&gameInfo);
8592     gameInfo.variant = StringToVariant(appData.variant);
8593     ics_user_moved = ics_clock_paused = FALSE;
8594     ics_getting_history = H_FALSE;
8595     ics_gamenum = -1;
8596     white_holding[0] = black_holding[0] = NULLCHAR;
8597     ClearProgramStats();
8598     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8599     
8600     ResetFrontEnd();
8601     ClearHighlights();
8602     flipView = appData.flipView;
8603     ClearPremoveHighlights();
8604     gotPremove = FALSE;
8605     alarmSounded = FALSE;
8606
8607     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8608     if(appData.serverMovesName != NULL) {
8609         /* [HGM] prepare to make moves file for broadcasting */
8610         clock_t t = clock();
8611         if(serverMoves != NULL) fclose(serverMoves);
8612         serverMoves = fopen(appData.serverMovesName, "r");
8613         if(serverMoves != NULL) {
8614             fclose(serverMoves);
8615             /* delay 15 sec before overwriting, so all clients can see end */
8616             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8617         }
8618         serverMoves = fopen(appData.serverMovesName, "w");
8619     }
8620
8621     ExitAnalyzeMode();
8622     gameMode = BeginningOfGame;
8623     ModeHighlight();
8624     if(appData.icsActive) gameInfo.variant = VariantNormal;
8625     currentMove = forwardMostMove = backwardMostMove = 0;
8626     InitPosition(redraw);
8627     for (i = 0; i < MAX_MOVES; i++) {
8628         if (commentList[i] != NULL) {
8629             free(commentList[i]);
8630             commentList[i] = NULL;
8631         }
8632     }
8633     ResetClocks();
8634     timeRemaining[0][0] = whiteTimeRemaining;
8635     timeRemaining[1][0] = blackTimeRemaining;
8636     if (first.pr == NULL) {
8637         StartChessProgram(&first);
8638     }
8639     if (init) {
8640             InitChessProgram(&first, startedFromSetupPosition);
8641     }
8642     DisplayTitle("");
8643     DisplayMessage("", "");
8644     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8645     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8646 }
8647
8648 void
8649 AutoPlayGameLoop()
8650 {
8651     for (;;) {
8652         if (!AutoPlayOneMove())
8653           return;
8654         if (matchMode || appData.timeDelay == 0)
8655           continue;
8656         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8657           return;
8658         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8659         break;
8660     }
8661 }
8662
8663
8664 int
8665 AutoPlayOneMove()
8666 {
8667     int fromX, fromY, toX, toY;
8668
8669     if (appData.debugMode) {
8670       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8671     }
8672
8673     if (gameMode != PlayFromGameFile)
8674       return FALSE;
8675
8676     if (currentMove >= forwardMostMove) {
8677       gameMode = EditGame;
8678       ModeHighlight();
8679
8680       /* [AS] Clear current move marker at the end of a game */
8681       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8682
8683       return FALSE;
8684     }
8685     
8686     toX = moveList[currentMove][2] - AAA;
8687     toY = moveList[currentMove][3] - ONE;
8688
8689     if (moveList[currentMove][1] == '@') {
8690         if (appData.highlightLastMove) {
8691             SetHighlights(-1, -1, toX, toY);
8692         }
8693     } else {
8694         fromX = moveList[currentMove][0] - AAA;
8695         fromY = moveList[currentMove][1] - ONE;
8696
8697         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8698
8699         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8700
8701         if (appData.highlightLastMove) {
8702             SetHighlights(fromX, fromY, toX, toY);
8703         }
8704     }
8705     DisplayMove(currentMove);
8706     SendMoveToProgram(currentMove++, &first);
8707     DisplayBothClocks();
8708     DrawPosition(FALSE, boards[currentMove]);
8709     // [HGM] PV info: always display, routine tests if empty
8710     DisplayComment(currentMove - 1, commentList[currentMove]);
8711     return TRUE;
8712 }
8713
8714
8715 int
8716 LoadGameOneMove(readAhead)
8717      ChessMove readAhead;
8718 {
8719     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8720     char promoChar = NULLCHAR;
8721     ChessMove moveType;
8722     char move[MSG_SIZ];
8723     char *p, *q;
8724     
8725     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8726         gameMode != AnalyzeMode && gameMode != Training) {
8727         gameFileFP = NULL;
8728         return FALSE;
8729     }
8730     
8731     yyboardindex = forwardMostMove;
8732     if (readAhead != (ChessMove)0) {
8733       moveType = readAhead;
8734     } else {
8735       if (gameFileFP == NULL)
8736           return FALSE;
8737       moveType = (ChessMove) yylex();
8738     }
8739     
8740     done = FALSE;
8741     switch (moveType) {
8742       case Comment:
8743         if (appData.debugMode) 
8744           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8745         p = yy_text;
8746
8747         /* append the comment but don't display it */
8748         AppendComment(currentMove, p, FALSE);
8749         return TRUE;
8750
8751       case WhiteCapturesEnPassant:
8752       case BlackCapturesEnPassant:
8753       case WhitePromotionChancellor:
8754       case BlackPromotionChancellor:
8755       case WhitePromotionArchbishop:
8756       case BlackPromotionArchbishop:
8757       case WhitePromotionCentaur:
8758       case BlackPromotionCentaur:
8759       case WhitePromotionQueen:
8760       case BlackPromotionQueen:
8761       case WhitePromotionRook:
8762       case BlackPromotionRook:
8763       case WhitePromotionBishop:
8764       case BlackPromotionBishop:
8765       case WhitePromotionKnight:
8766       case BlackPromotionKnight:
8767       case WhitePromotionKing:
8768       case BlackPromotionKing:
8769       case NormalMove:
8770       case WhiteKingSideCastle:
8771       case WhiteQueenSideCastle:
8772       case BlackKingSideCastle:
8773       case BlackQueenSideCastle:
8774       case WhiteKingSideCastleWild:
8775       case WhiteQueenSideCastleWild:
8776       case BlackKingSideCastleWild:
8777       case BlackQueenSideCastleWild:
8778       /* PUSH Fabien */
8779       case WhiteHSideCastleFR:
8780       case WhiteASideCastleFR:
8781       case BlackHSideCastleFR:
8782       case BlackASideCastleFR:
8783       /* POP Fabien */
8784         if (appData.debugMode)
8785           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8786         fromX = currentMoveString[0] - AAA;
8787         fromY = currentMoveString[1] - ONE;
8788         toX = currentMoveString[2] - AAA;
8789         toY = currentMoveString[3] - ONE;
8790         promoChar = currentMoveString[4];
8791         break;
8792
8793       case WhiteDrop:
8794       case BlackDrop:
8795         if (appData.debugMode)
8796           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8797         fromX = moveType == WhiteDrop ?
8798           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8799         (int) CharToPiece(ToLower(currentMoveString[0]));
8800         fromY = DROP_RANK;
8801         toX = currentMoveString[2] - AAA;
8802         toY = currentMoveString[3] - ONE;
8803         break;
8804
8805       case WhiteWins:
8806       case BlackWins:
8807       case GameIsDrawn:
8808       case GameUnfinished:
8809         if (appData.debugMode)
8810           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8811         p = strchr(yy_text, '{');
8812         if (p == NULL) p = strchr(yy_text, '(');
8813         if (p == NULL) {
8814             p = yy_text;
8815             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8816         } else {
8817             q = strchr(p, *p == '{' ? '}' : ')');
8818             if (q != NULL) *q = NULLCHAR;
8819             p++;
8820         }
8821         GameEnds(moveType, p, GE_FILE);
8822         done = TRUE;
8823         if (cmailMsgLoaded) {
8824             ClearHighlights();
8825             flipView = WhiteOnMove(currentMove);
8826             if (moveType == GameUnfinished) flipView = !flipView;
8827             if (appData.debugMode)
8828               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8829         }
8830         break;
8831
8832       case (ChessMove) 0:       /* end of file */
8833         if (appData.debugMode)
8834           fprintf(debugFP, "Parser hit end of file\n");
8835         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8836           case MT_NONE:
8837           case MT_CHECK:
8838             break;
8839           case MT_CHECKMATE:
8840           case MT_STAINMATE:
8841             if (WhiteOnMove(currentMove)) {
8842                 GameEnds(BlackWins, "Black mates", GE_FILE);
8843             } else {
8844                 GameEnds(WhiteWins, "White mates", GE_FILE);
8845             }
8846             break;
8847           case MT_STALEMATE:
8848             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8849             break;
8850         }
8851         done = TRUE;
8852         break;
8853
8854       case MoveNumberOne:
8855         if (lastLoadGameStart == GNUChessGame) {
8856             /* GNUChessGames have numbers, but they aren't move numbers */
8857             if (appData.debugMode)
8858               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8859                       yy_text, (int) moveType);
8860             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8861         }
8862         /* else fall thru */
8863
8864       case XBoardGame:
8865       case GNUChessGame:
8866       case PGNTag:
8867         /* Reached start of next game in file */
8868         if (appData.debugMode)
8869           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8870         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8871           case MT_NONE:
8872           case MT_CHECK:
8873             break;
8874           case MT_CHECKMATE:
8875           case MT_STAINMATE:
8876             if (WhiteOnMove(currentMove)) {
8877                 GameEnds(BlackWins, "Black mates", GE_FILE);
8878             } else {
8879                 GameEnds(WhiteWins, "White mates", GE_FILE);
8880             }
8881             break;
8882           case MT_STALEMATE:
8883             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8884             break;
8885         }
8886         done = TRUE;
8887         break;
8888
8889       case PositionDiagram:     /* should not happen; ignore */
8890       case ElapsedTime:         /* ignore */
8891       case NAG:                 /* ignore */
8892         if (appData.debugMode)
8893           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8894                   yy_text, (int) moveType);
8895         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8896
8897       case IllegalMove:
8898         if (appData.testLegality) {
8899             if (appData.debugMode)
8900               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8901             sprintf(move, _("Illegal move: %d.%s%s"),
8902                     (forwardMostMove / 2) + 1,
8903                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8904             DisplayError(move, 0);
8905             done = TRUE;
8906         } else {
8907             if (appData.debugMode)
8908               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8909                       yy_text, currentMoveString);
8910             fromX = currentMoveString[0] - AAA;
8911             fromY = currentMoveString[1] - ONE;
8912             toX = currentMoveString[2] - AAA;
8913             toY = currentMoveString[3] - ONE;
8914             promoChar = currentMoveString[4];
8915         }
8916         break;
8917
8918       case AmbiguousMove:
8919         if (appData.debugMode)
8920           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8921         sprintf(move, _("Ambiguous move: %d.%s%s"),
8922                 (forwardMostMove / 2) + 1,
8923                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8924         DisplayError(move, 0);
8925         done = TRUE;
8926         break;
8927
8928       default:
8929       case ImpossibleMove:
8930         if (appData.debugMode)
8931           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8932         sprintf(move, _("Illegal move: %d.%s%s"),
8933                 (forwardMostMove / 2) + 1,
8934                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8935         DisplayError(move, 0);
8936         done = TRUE;
8937         break;
8938     }
8939
8940     if (done) {
8941         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8942             DrawPosition(FALSE, boards[currentMove]);
8943             DisplayBothClocks();
8944             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8945               DisplayComment(currentMove - 1, commentList[currentMove]);
8946         }
8947         (void) StopLoadGameTimer();
8948         gameFileFP = NULL;
8949         cmailOldMove = forwardMostMove;
8950         return FALSE;
8951     } else {
8952         /* currentMoveString is set as a side-effect of yylex */
8953         strcat(currentMoveString, "\n");
8954         strcpy(moveList[forwardMostMove], currentMoveString);
8955         
8956         thinkOutput[0] = NULLCHAR;
8957         MakeMove(fromX, fromY, toX, toY, promoChar);
8958         currentMove = forwardMostMove;
8959         return TRUE;
8960     }
8961 }
8962
8963 /* Load the nth game from the given file */
8964 int
8965 LoadGameFromFile(filename, n, title, useList)
8966      char *filename;
8967      int n;
8968      char *title;
8969      /*Boolean*/ int useList;
8970 {
8971     FILE *f;
8972     char buf[MSG_SIZ];
8973
8974     if (strcmp(filename, "-") == 0) {
8975         f = stdin;
8976         title = "stdin";
8977     } else {
8978         f = fopen(filename, "rb");
8979         if (f == NULL) {
8980           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8981             DisplayError(buf, errno);
8982             return FALSE;
8983         }
8984     }
8985     if (fseek(f, 0, 0) == -1) {
8986         /* f is not seekable; probably a pipe */
8987         useList = FALSE;
8988     }
8989     if (useList && n == 0) {
8990         int error = GameListBuild(f);
8991         if (error) {
8992             DisplayError(_("Cannot build game list"), error);
8993         } else if (!ListEmpty(&gameList) &&
8994                    ((ListGame *) gameList.tailPred)->number > 1) {
8995             GameListPopUp(f, title);
8996             return TRUE;
8997         }
8998         GameListDestroy();
8999         n = 1;
9000     }
9001     if (n == 0) n = 1;
9002     return LoadGame(f, n, title, FALSE);
9003 }
9004
9005
9006 void
9007 MakeRegisteredMove()
9008 {
9009     int fromX, fromY, toX, toY;
9010     char promoChar;
9011     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9012         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9013           case CMAIL_MOVE:
9014           case CMAIL_DRAW:
9015             if (appData.debugMode)
9016               fprintf(debugFP, "Restoring %s for game %d\n",
9017                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9018     
9019             thinkOutput[0] = NULLCHAR;
9020             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9021             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9022             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9023             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9024             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9025             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9026             MakeMove(fromX, fromY, toX, toY, promoChar);
9027             ShowMove(fromX, fromY, toX, toY);
9028               
9029             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9030               case MT_NONE:
9031               case MT_CHECK:
9032                 break;
9033                 
9034               case MT_CHECKMATE:
9035               case MT_STAINMATE:
9036                 if (WhiteOnMove(currentMove)) {
9037                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9038                 } else {
9039                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9040                 }
9041                 break;
9042                 
9043               case MT_STALEMATE:
9044                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9045                 break;
9046             }
9047
9048             break;
9049             
9050           case CMAIL_RESIGN:
9051             if (WhiteOnMove(currentMove)) {
9052                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9053             } else {
9054                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9055             }
9056             break;
9057             
9058           case CMAIL_ACCEPT:
9059             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9060             break;
9061               
9062           default:
9063             break;
9064         }
9065     }
9066
9067     return;
9068 }
9069
9070 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9071 int
9072 CmailLoadGame(f, gameNumber, title, useList)
9073      FILE *f;
9074      int gameNumber;
9075      char *title;
9076      int useList;
9077 {
9078     int retVal;
9079
9080     if (gameNumber > nCmailGames) {
9081         DisplayError(_("No more games in this message"), 0);
9082         return FALSE;
9083     }
9084     if (f == lastLoadGameFP) {
9085         int offset = gameNumber - lastLoadGameNumber;
9086         if (offset == 0) {
9087             cmailMsg[0] = NULLCHAR;
9088             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9089                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9090                 nCmailMovesRegistered--;
9091             }
9092             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9093             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9094                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9095             }
9096         } else {
9097             if (! RegisterMove()) return FALSE;
9098         }
9099     }
9100
9101     retVal = LoadGame(f, gameNumber, title, useList);
9102
9103     /* Make move registered during previous look at this game, if any */
9104     MakeRegisteredMove();
9105
9106     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9107         commentList[currentMove]
9108           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9109         DisplayComment(currentMove - 1, commentList[currentMove]);
9110     }
9111
9112     return retVal;
9113 }
9114
9115 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9116 int
9117 ReloadGame(offset)
9118      int offset;
9119 {
9120     int gameNumber = lastLoadGameNumber + offset;
9121     if (lastLoadGameFP == NULL) {
9122         DisplayError(_("No game has been loaded yet"), 0);
9123         return FALSE;
9124     }
9125     if (gameNumber <= 0) {
9126         DisplayError(_("Can't back up any further"), 0);
9127         return FALSE;
9128     }
9129     if (cmailMsgLoaded) {
9130         return CmailLoadGame(lastLoadGameFP, gameNumber,
9131                              lastLoadGameTitle, lastLoadGameUseList);
9132     } else {
9133         return LoadGame(lastLoadGameFP, gameNumber,
9134                         lastLoadGameTitle, lastLoadGameUseList);
9135     }
9136 }
9137
9138
9139
9140 /* Load the nth game from open file f */
9141 int
9142 LoadGame(f, gameNumber, title, useList)
9143      FILE *f;
9144      int gameNumber;
9145      char *title;
9146      int useList;
9147 {
9148     ChessMove cm;
9149     char buf[MSG_SIZ];
9150     int gn = gameNumber;
9151     ListGame *lg = NULL;
9152     int numPGNTags = 0;
9153     int err;
9154     GameMode oldGameMode;
9155     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9156
9157     if (appData.debugMode) 
9158         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9159
9160     if (gameMode == Training )
9161         SetTrainingModeOff();
9162
9163     oldGameMode = gameMode;
9164     if (gameMode != BeginningOfGame) {
9165       Reset(FALSE, TRUE);
9166     }
9167
9168     gameFileFP = f;
9169     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9170         fclose(lastLoadGameFP);
9171     }
9172
9173     if (useList) {
9174         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9175         
9176         if (lg) {
9177             fseek(f, lg->offset, 0);
9178             GameListHighlight(gameNumber);
9179             gn = 1;
9180         }
9181         else {
9182             DisplayError(_("Game number out of range"), 0);
9183             return FALSE;
9184         }
9185     } else {
9186         GameListDestroy();
9187         if (fseek(f, 0, 0) == -1) {
9188             if (f == lastLoadGameFP ?
9189                 gameNumber == lastLoadGameNumber + 1 :
9190                 gameNumber == 1) {
9191                 gn = 1;
9192             } else {
9193                 DisplayError(_("Can't seek on game file"), 0);
9194                 return FALSE;
9195             }
9196         }
9197     }
9198     lastLoadGameFP = f;
9199     lastLoadGameNumber = gameNumber;
9200     strcpy(lastLoadGameTitle, title);
9201     lastLoadGameUseList = useList;
9202
9203     yynewfile(f);
9204
9205     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9206       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9207                 lg->gameInfo.black);
9208             DisplayTitle(buf);
9209     } else if (*title != NULLCHAR) {
9210         if (gameNumber > 1) {
9211             sprintf(buf, "%s %d", title, gameNumber);
9212             DisplayTitle(buf);
9213         } else {
9214             DisplayTitle(title);
9215         }
9216     }
9217
9218     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9219         gameMode = PlayFromGameFile;
9220         ModeHighlight();
9221     }
9222
9223     currentMove = forwardMostMove = backwardMostMove = 0;
9224     CopyBoard(boards[0], initialPosition);
9225     StopClocks();
9226
9227     /*
9228      * Skip the first gn-1 games in the file.
9229      * Also skip over anything that precedes an identifiable 
9230      * start of game marker, to avoid being confused by 
9231      * garbage at the start of the file.  Currently 
9232      * recognized start of game markers are the move number "1",
9233      * the pattern "gnuchess .* game", the pattern
9234      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9235      * A game that starts with one of the latter two patterns
9236      * will also have a move number 1, possibly
9237      * following a position diagram.
9238      * 5-4-02: Let's try being more lenient and allowing a game to
9239      * start with an unnumbered move.  Does that break anything?
9240      */
9241     cm = lastLoadGameStart = (ChessMove) 0;
9242     while (gn > 0) {
9243         yyboardindex = forwardMostMove;
9244         cm = (ChessMove) yylex();
9245         switch (cm) {
9246           case (ChessMove) 0:
9247             if (cmailMsgLoaded) {
9248                 nCmailGames = CMAIL_MAX_GAMES - gn;
9249             } else {
9250                 Reset(TRUE, TRUE);
9251                 DisplayError(_("Game not found in file"), 0);
9252             }
9253             return FALSE;
9254
9255           case GNUChessGame:
9256           case XBoardGame:
9257             gn--;
9258             lastLoadGameStart = cm;
9259             break;
9260             
9261           case MoveNumberOne:
9262             switch (lastLoadGameStart) {
9263               case GNUChessGame:
9264               case XBoardGame:
9265               case PGNTag:
9266                 break;
9267               case MoveNumberOne:
9268               case (ChessMove) 0:
9269                 gn--;           /* count this game */
9270                 lastLoadGameStart = cm;
9271                 break;
9272               default:
9273                 /* impossible */
9274                 break;
9275             }
9276             break;
9277
9278           case PGNTag:
9279             switch (lastLoadGameStart) {
9280               case GNUChessGame:
9281               case PGNTag:
9282               case MoveNumberOne:
9283               case (ChessMove) 0:
9284                 gn--;           /* count this game */
9285                 lastLoadGameStart = cm;
9286                 break;
9287               case XBoardGame:
9288                 lastLoadGameStart = cm; /* game counted already */
9289                 break;
9290               default:
9291                 /* impossible */
9292                 break;
9293             }
9294             if (gn > 0) {
9295                 do {
9296                     yyboardindex = forwardMostMove;
9297                     cm = (ChessMove) yylex();
9298                 } while (cm == PGNTag || cm == Comment);
9299             }
9300             break;
9301
9302           case WhiteWins:
9303           case BlackWins:
9304           case GameIsDrawn:
9305             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9306                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9307                     != CMAIL_OLD_RESULT) {
9308                     nCmailResults ++ ;
9309                     cmailResult[  CMAIL_MAX_GAMES
9310                                 - gn - 1] = CMAIL_OLD_RESULT;
9311                 }
9312             }
9313             break;
9314
9315           case NormalMove:
9316             /* Only a NormalMove can be at the start of a game
9317              * without a position diagram. */
9318             if (lastLoadGameStart == (ChessMove) 0) {
9319               gn--;
9320               lastLoadGameStart = MoveNumberOne;
9321             }
9322             break;
9323
9324           default:
9325             break;
9326         }
9327     }
9328     
9329     if (appData.debugMode)
9330       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9331
9332     if (cm == XBoardGame) {
9333         /* Skip any header junk before position diagram and/or move 1 */
9334         for (;;) {
9335             yyboardindex = forwardMostMove;
9336             cm = (ChessMove) yylex();
9337
9338             if (cm == (ChessMove) 0 ||
9339                 cm == GNUChessGame || cm == XBoardGame) {
9340                 /* Empty game; pretend end-of-file and handle later */
9341                 cm = (ChessMove) 0;
9342                 break;
9343             }
9344
9345             if (cm == MoveNumberOne || cm == PositionDiagram ||
9346                 cm == PGNTag || cm == Comment)
9347               break;
9348         }
9349     } else if (cm == GNUChessGame) {
9350         if (gameInfo.event != NULL) {
9351             free(gameInfo.event);
9352         }
9353         gameInfo.event = StrSave(yy_text);
9354     }   
9355
9356     startedFromSetupPosition = FALSE;
9357     while (cm == PGNTag) {
9358         if (appData.debugMode) 
9359           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9360         err = ParsePGNTag(yy_text, &gameInfo);
9361         if (!err) numPGNTags++;
9362
9363         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9364         if(gameInfo.variant != oldVariant) {
9365             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9366             InitPosition(TRUE);
9367             oldVariant = gameInfo.variant;
9368             if (appData.debugMode) 
9369               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9370         }
9371
9372
9373         if (gameInfo.fen != NULL) {
9374           Board initial_position;
9375           startedFromSetupPosition = TRUE;
9376           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9377             Reset(TRUE, TRUE);
9378             DisplayError(_("Bad FEN position in file"), 0);
9379             return FALSE;
9380           }
9381           CopyBoard(boards[0], initial_position);
9382           if (blackPlaysFirst) {
9383             currentMove = forwardMostMove = backwardMostMove = 1;
9384             CopyBoard(boards[1], initial_position);
9385             strcpy(moveList[0], "");
9386             strcpy(parseList[0], "");
9387             timeRemaining[0][1] = whiteTimeRemaining;
9388             timeRemaining[1][1] = blackTimeRemaining;
9389             if (commentList[0] != NULL) {
9390               commentList[1] = commentList[0];
9391               commentList[0] = NULL;
9392             }
9393           } else {
9394             currentMove = forwardMostMove = backwardMostMove = 0;
9395           }
9396           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9397           {   int i;
9398               initialRulePlies = FENrulePlies;
9399               for( i=0; i< nrCastlingRights; i++ )
9400                   initialRights[i] = initial_position[CASTLING][i];
9401           }
9402           yyboardindex = forwardMostMove;
9403           free(gameInfo.fen);
9404           gameInfo.fen = NULL;
9405         }
9406
9407         yyboardindex = forwardMostMove;
9408         cm = (ChessMove) yylex();
9409
9410         /* Handle comments interspersed among the tags */
9411         while (cm == Comment) {
9412             char *p;
9413             if (appData.debugMode) 
9414               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9415             p = yy_text;
9416             AppendComment(currentMove, p, FALSE);
9417             yyboardindex = forwardMostMove;
9418             cm = (ChessMove) yylex();
9419         }
9420     }
9421
9422     /* don't rely on existence of Event tag since if game was
9423      * pasted from clipboard the Event tag may not exist
9424      */
9425     if (numPGNTags > 0){
9426         char *tags;
9427         if (gameInfo.variant == VariantNormal) {
9428           gameInfo.variant = StringToVariant(gameInfo.event);
9429         }
9430         if (!matchMode) {
9431           if( appData.autoDisplayTags ) {
9432             tags = PGNTags(&gameInfo);
9433             TagsPopUp(tags, CmailMsg());
9434             free(tags);
9435           }
9436         }
9437     } else {
9438         /* Make something up, but don't display it now */
9439         SetGameInfo();
9440         TagsPopDown();
9441     }
9442
9443     if (cm == PositionDiagram) {
9444         int i, j;
9445         char *p;
9446         Board initial_position;
9447
9448         if (appData.debugMode)
9449           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9450
9451         if (!startedFromSetupPosition) {
9452             p = yy_text;
9453             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9454               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9455                 switch (*p) {
9456                   case '[':
9457                   case '-':
9458                   case ' ':
9459                   case '\t':
9460                   case '\n':
9461                   case '\r':
9462                     break;
9463                   default:
9464                     initial_position[i][j++] = CharToPiece(*p);
9465                     break;
9466                 }
9467             while (*p == ' ' || *p == '\t' ||
9468                    *p == '\n' || *p == '\r') p++;
9469         
9470             if (strncmp(p, "black", strlen("black"))==0)
9471               blackPlaysFirst = TRUE;
9472             else
9473               blackPlaysFirst = FALSE;
9474             startedFromSetupPosition = TRUE;
9475         
9476             CopyBoard(boards[0], initial_position);
9477             if (blackPlaysFirst) {
9478                 currentMove = forwardMostMove = backwardMostMove = 1;
9479                 CopyBoard(boards[1], initial_position);
9480                 strcpy(moveList[0], "");
9481                 strcpy(parseList[0], "");
9482                 timeRemaining[0][1] = whiteTimeRemaining;
9483                 timeRemaining[1][1] = blackTimeRemaining;
9484                 if (commentList[0] != NULL) {
9485                     commentList[1] = commentList[0];
9486                     commentList[0] = NULL;
9487                 }
9488             } else {
9489                 currentMove = forwardMostMove = backwardMostMove = 0;
9490             }
9491         }
9492         yyboardindex = forwardMostMove;
9493         cm = (ChessMove) yylex();
9494     }
9495
9496     if (first.pr == NoProc) {
9497         StartChessProgram(&first);
9498     }
9499     InitChessProgram(&first, FALSE);
9500     SendToProgram("force\n", &first);
9501     if (startedFromSetupPosition) {
9502         SendBoard(&first, forwardMostMove);
9503     if (appData.debugMode) {
9504         fprintf(debugFP, "Load Game\n");
9505     }
9506         DisplayBothClocks();
9507     }      
9508
9509     /* [HGM] server: flag to write setup moves in broadcast file as one */
9510     loadFlag = appData.suppressLoadMoves;
9511
9512     while (cm == Comment) {
9513         char *p;
9514         if (appData.debugMode) 
9515           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9516         p = yy_text;
9517         AppendComment(currentMove, p, FALSE);
9518         yyboardindex = forwardMostMove;
9519         cm = (ChessMove) yylex();
9520     }
9521
9522     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9523         cm == WhiteWins || cm == BlackWins ||
9524         cm == GameIsDrawn || cm == GameUnfinished) {
9525         DisplayMessage("", _("No moves in game"));
9526         if (cmailMsgLoaded) {
9527             if (appData.debugMode)
9528               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9529             ClearHighlights();
9530             flipView = FALSE;
9531         }
9532         DrawPosition(FALSE, boards[currentMove]);
9533         DisplayBothClocks();
9534         gameMode = EditGame;
9535         ModeHighlight();
9536         gameFileFP = NULL;
9537         cmailOldMove = 0;
9538         return TRUE;
9539     }
9540
9541     // [HGM] PV info: routine tests if comment empty
9542     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9543         DisplayComment(currentMove - 1, commentList[currentMove]);
9544     }
9545     if (!matchMode && appData.timeDelay != 0) 
9546       DrawPosition(FALSE, boards[currentMove]);
9547
9548     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9549       programStats.ok_to_send = 1;
9550     }
9551
9552     /* if the first token after the PGN tags is a move
9553      * and not move number 1, retrieve it from the parser 
9554      */
9555     if (cm != MoveNumberOne)
9556         LoadGameOneMove(cm);
9557
9558     /* load the remaining moves from the file */
9559     while (LoadGameOneMove((ChessMove)0)) {
9560       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9561       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9562     }
9563
9564     /* rewind to the start of the game */
9565     currentMove = backwardMostMove;
9566
9567     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9568
9569     if (oldGameMode == AnalyzeFile ||
9570         oldGameMode == AnalyzeMode) {
9571       AnalyzeFileEvent();
9572     }
9573
9574     if (matchMode || appData.timeDelay == 0) {
9575       ToEndEvent();
9576       gameMode = EditGame;
9577       ModeHighlight();
9578     } else if (appData.timeDelay > 0) {
9579       AutoPlayGameLoop();
9580     }
9581
9582     if (appData.debugMode) 
9583         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9584
9585     loadFlag = 0; /* [HGM] true game starts */
9586     return TRUE;
9587 }
9588
9589 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9590 int
9591 ReloadPosition(offset)
9592      int offset;
9593 {
9594     int positionNumber = lastLoadPositionNumber + offset;
9595     if (lastLoadPositionFP == NULL) {
9596         DisplayError(_("No position has been loaded yet"), 0);
9597         return FALSE;
9598     }
9599     if (positionNumber <= 0) {
9600         DisplayError(_("Can't back up any further"), 0);
9601         return FALSE;
9602     }
9603     return LoadPosition(lastLoadPositionFP, positionNumber,
9604                         lastLoadPositionTitle);
9605 }
9606
9607 /* Load the nth position from the given file */
9608 int
9609 LoadPositionFromFile(filename, n, title)
9610      char *filename;
9611      int n;
9612      char *title;
9613 {
9614     FILE *f;
9615     char buf[MSG_SIZ];
9616
9617     if (strcmp(filename, "-") == 0) {
9618         return LoadPosition(stdin, n, "stdin");
9619     } else {
9620         f = fopen(filename, "rb");
9621         if (f == NULL) {
9622             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9623             DisplayError(buf, errno);
9624             return FALSE;
9625         } else {
9626             return LoadPosition(f, n, title);
9627         }
9628     }
9629 }
9630
9631 /* Load the nth position from the given open file, and close it */
9632 int
9633 LoadPosition(f, positionNumber, title)
9634      FILE *f;
9635      int positionNumber;
9636      char *title;
9637 {
9638     char *p, line[MSG_SIZ];
9639     Board initial_position;
9640     int i, j, fenMode, pn;
9641     
9642     if (gameMode == Training )
9643         SetTrainingModeOff();
9644
9645     if (gameMode != BeginningOfGame) {
9646         Reset(FALSE, TRUE);
9647     }
9648     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9649         fclose(lastLoadPositionFP);
9650     }
9651     if (positionNumber == 0) positionNumber = 1;
9652     lastLoadPositionFP = f;
9653     lastLoadPositionNumber = positionNumber;
9654     strcpy(lastLoadPositionTitle, title);
9655     if (first.pr == NoProc) {
9656       StartChessProgram(&first);
9657       InitChessProgram(&first, FALSE);
9658     }    
9659     pn = positionNumber;
9660     if (positionNumber < 0) {
9661         /* Negative position number means to seek to that byte offset */
9662         if (fseek(f, -positionNumber, 0) == -1) {
9663             DisplayError(_("Can't seek on position file"), 0);
9664             return FALSE;
9665         };
9666         pn = 1;
9667     } else {
9668         if (fseek(f, 0, 0) == -1) {
9669             if (f == lastLoadPositionFP ?
9670                 positionNumber == lastLoadPositionNumber + 1 :
9671                 positionNumber == 1) {
9672                 pn = 1;
9673             } else {
9674                 DisplayError(_("Can't seek on position file"), 0);
9675                 return FALSE;
9676             }
9677         }
9678     }
9679     /* See if this file is FEN or old-style xboard */
9680     if (fgets(line, MSG_SIZ, f) == NULL) {
9681         DisplayError(_("Position not found in file"), 0);
9682         return FALSE;
9683     }
9684     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9685     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9686
9687     if (pn >= 2) {
9688         if (fenMode || line[0] == '#') pn--;
9689         while (pn > 0) {
9690             /* skip positions before number pn */
9691             if (fgets(line, MSG_SIZ, f) == NULL) {
9692                 Reset(TRUE, TRUE);
9693                 DisplayError(_("Position not found in file"), 0);
9694                 return FALSE;
9695             }
9696             if (fenMode || line[0] == '#') pn--;
9697         }
9698     }
9699
9700     if (fenMode) {
9701         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9702             DisplayError(_("Bad FEN position in file"), 0);
9703             return FALSE;
9704         }
9705     } else {
9706         (void) fgets(line, MSG_SIZ, f);
9707         (void) fgets(line, MSG_SIZ, f);
9708     
9709         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9710             (void) fgets(line, MSG_SIZ, f);
9711             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9712                 if (*p == ' ')
9713                   continue;
9714                 initial_position[i][j++] = CharToPiece(*p);
9715             }
9716         }
9717     
9718         blackPlaysFirst = FALSE;
9719         if (!feof(f)) {
9720             (void) fgets(line, MSG_SIZ, f);
9721             if (strncmp(line, "black", strlen("black"))==0)
9722               blackPlaysFirst = TRUE;
9723         }
9724     }
9725     startedFromSetupPosition = TRUE;
9726     
9727     SendToProgram("force\n", &first);
9728     CopyBoard(boards[0], initial_position);
9729     if (blackPlaysFirst) {
9730         currentMove = forwardMostMove = backwardMostMove = 1;
9731         strcpy(moveList[0], "");
9732         strcpy(parseList[0], "");
9733         CopyBoard(boards[1], initial_position);
9734         DisplayMessage("", _("Black to play"));
9735     } else {
9736         currentMove = forwardMostMove = backwardMostMove = 0;
9737         DisplayMessage("", _("White to play"));
9738     }
9739     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9740     SendBoard(&first, forwardMostMove);
9741     if (appData.debugMode) {
9742 int i, j;
9743   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9744   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9745         fprintf(debugFP, "Load Position\n");
9746     }
9747
9748     if (positionNumber > 1) {
9749         sprintf(line, "%s %d", title, positionNumber);
9750         DisplayTitle(line);
9751     } else {
9752         DisplayTitle(title);
9753     }
9754     gameMode = EditGame;
9755     ModeHighlight();
9756     ResetClocks();
9757     timeRemaining[0][1] = whiteTimeRemaining;
9758     timeRemaining[1][1] = blackTimeRemaining;
9759     DrawPosition(FALSE, boards[currentMove]);
9760    
9761     return TRUE;
9762 }
9763
9764
9765 void
9766 CopyPlayerNameIntoFileName(dest, src)
9767      char **dest, *src;
9768 {
9769     while (*src != NULLCHAR && *src != ',') {
9770         if (*src == ' ') {
9771             *(*dest)++ = '_';
9772             src++;
9773         } else {
9774             *(*dest)++ = *src++;
9775         }
9776     }
9777 }
9778
9779 char *DefaultFileName(ext)
9780      char *ext;
9781 {
9782     static char def[MSG_SIZ];
9783     char *p;
9784
9785     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9786         p = def;
9787         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9788         *p++ = '-';
9789         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9790         *p++ = '.';
9791         strcpy(p, ext);
9792     } else {
9793         def[0] = NULLCHAR;
9794     }
9795     return def;
9796 }
9797
9798 /* Save the current game to the given file */
9799 int
9800 SaveGameToFile(filename, append)
9801      char *filename;
9802      int append;
9803 {
9804     FILE *f;
9805     char buf[MSG_SIZ];
9806
9807     if (strcmp(filename, "-") == 0) {
9808         return SaveGame(stdout, 0, NULL);
9809     } else {
9810         f = fopen(filename, append ? "a" : "w");
9811         if (f == NULL) {
9812             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9813             DisplayError(buf, errno);
9814             return FALSE;
9815         } else {
9816             return SaveGame(f, 0, NULL);
9817         }
9818     }
9819 }
9820
9821 char *
9822 SavePart(str)
9823      char *str;
9824 {
9825     static char buf[MSG_SIZ];
9826     char *p;
9827     
9828     p = strchr(str, ' ');
9829     if (p == NULL) return str;
9830     strncpy(buf, str, p - str);
9831     buf[p - str] = NULLCHAR;
9832     return buf;
9833 }
9834
9835 #define PGN_MAX_LINE 75
9836
9837 #define PGN_SIDE_WHITE  0
9838 #define PGN_SIDE_BLACK  1
9839
9840 /* [AS] */
9841 static int FindFirstMoveOutOfBook( int side )
9842 {
9843     int result = -1;
9844
9845     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9846         int index = backwardMostMove;
9847         int has_book_hit = 0;
9848
9849         if( (index % 2) != side ) {
9850             index++;
9851         }
9852
9853         while( index < forwardMostMove ) {
9854             /* Check to see if engine is in book */
9855             int depth = pvInfoList[index].depth;
9856             int score = pvInfoList[index].score;
9857             int in_book = 0;
9858
9859             if( depth <= 2 ) {
9860                 in_book = 1;
9861             }
9862             else if( score == 0 && depth == 63 ) {
9863                 in_book = 1; /* Zappa */
9864             }
9865             else if( score == 2 && depth == 99 ) {
9866                 in_book = 1; /* Abrok */
9867             }
9868
9869             has_book_hit += in_book;
9870
9871             if( ! in_book ) {
9872                 result = index;
9873
9874                 break;
9875             }
9876
9877             index += 2;
9878         }
9879     }
9880
9881     return result;
9882 }
9883
9884 /* [AS] */
9885 void GetOutOfBookInfo( char * buf )
9886 {
9887     int oob[2];
9888     int i;
9889     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9890
9891     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9892     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9893
9894     *buf = '\0';
9895
9896     if( oob[0] >= 0 || oob[1] >= 0 ) {
9897         for( i=0; i<2; i++ ) {
9898             int idx = oob[i];
9899
9900             if( idx >= 0 ) {
9901                 if( i > 0 && oob[0] >= 0 ) {
9902                     strcat( buf, "   " );
9903                 }
9904
9905                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9906                 sprintf( buf+strlen(buf), "%s%.2f", 
9907                     pvInfoList[idx].score >= 0 ? "+" : "",
9908                     pvInfoList[idx].score / 100.0 );
9909             }
9910         }
9911     }
9912 }
9913
9914 /* Save game in PGN style and close the file */
9915 int
9916 SaveGamePGN(f)
9917      FILE *f;
9918 {
9919     int i, offset, linelen, newblock;
9920     time_t tm;
9921 //    char *movetext;
9922     char numtext[32];
9923     int movelen, numlen, blank;
9924     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9925
9926     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9927     
9928     tm = time((time_t *) NULL);
9929     
9930     PrintPGNTags(f, &gameInfo);
9931     
9932     if (backwardMostMove > 0 || startedFromSetupPosition) {
9933         char *fen = PositionToFEN(backwardMostMove, NULL);
9934         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9935         fprintf(f, "\n{--------------\n");
9936         PrintPosition(f, backwardMostMove);
9937         fprintf(f, "--------------}\n");
9938         free(fen);
9939     }
9940     else {
9941         /* [AS] Out of book annotation */
9942         if( appData.saveOutOfBookInfo ) {
9943             char buf[64];
9944
9945             GetOutOfBookInfo( buf );
9946
9947             if( buf[0] != '\0' ) {
9948                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9949             }
9950         }
9951
9952         fprintf(f, "\n");
9953     }
9954
9955     i = backwardMostMove;
9956     linelen = 0;
9957     newblock = TRUE;
9958
9959     while (i < forwardMostMove) {
9960         /* Print comments preceding this move */
9961         if (commentList[i] != NULL) {
9962             if (linelen > 0) fprintf(f, "\n");
9963             fprintf(f, "%s", commentList[i]);
9964             linelen = 0;
9965             newblock = TRUE;
9966         }
9967
9968         /* Format move number */
9969         if ((i % 2) == 0) {
9970             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9971         } else {
9972             if (newblock) {
9973                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9974             } else {
9975                 numtext[0] = NULLCHAR;
9976             }
9977         }
9978         numlen = strlen(numtext);
9979         newblock = FALSE;
9980
9981         /* Print move number */
9982         blank = linelen > 0 && numlen > 0;
9983         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9984             fprintf(f, "\n");
9985             linelen = 0;
9986             blank = 0;
9987         }
9988         if (blank) {
9989             fprintf(f, " ");
9990             linelen++;
9991         }
9992         fprintf(f, "%s", numtext);
9993         linelen += numlen;
9994
9995         /* Get move */
9996         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9997         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9998
9999         /* Print move */
10000         blank = linelen > 0 && movelen > 0;
10001         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10002             fprintf(f, "\n");
10003             linelen = 0;
10004             blank = 0;
10005         }
10006         if (blank) {
10007             fprintf(f, " ");
10008             linelen++;
10009         }
10010         fprintf(f, "%s", move_buffer);
10011         linelen += movelen;
10012
10013         /* [AS] Add PV info if present */
10014         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10015             /* [HGM] add time */
10016             char buf[MSG_SIZ]; int seconds;
10017
10018             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10019
10020             if( seconds <= 0) buf[0] = 0; else
10021             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10022                 seconds = (seconds + 4)/10; // round to full seconds
10023                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10024                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10025             }
10026
10027             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10028                 pvInfoList[i].score >= 0 ? "+" : "",
10029                 pvInfoList[i].score / 100.0,
10030                 pvInfoList[i].depth,
10031                 buf );
10032
10033             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10034
10035             /* Print score/depth */
10036             blank = linelen > 0 && movelen > 0;
10037             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10038                 fprintf(f, "\n");
10039                 linelen = 0;
10040                 blank = 0;
10041             }
10042             if (blank) {
10043                 fprintf(f, " ");
10044                 linelen++;
10045             }
10046             fprintf(f, "%s", move_buffer);
10047             linelen += movelen;
10048         }
10049
10050         i++;
10051     }
10052     
10053     /* Start a new line */
10054     if (linelen > 0) fprintf(f, "\n");
10055
10056     /* Print comments after last move */
10057     if (commentList[i] != NULL) {
10058         fprintf(f, "%s\n", commentList[i]);
10059     }
10060
10061     /* Print result */
10062     if (gameInfo.resultDetails != NULL &&
10063         gameInfo.resultDetails[0] != NULLCHAR) {
10064         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10065                 PGNResult(gameInfo.result));
10066     } else {
10067         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10068     }
10069
10070     fclose(f);
10071     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10072     return TRUE;
10073 }
10074
10075 /* Save game in old style and close the file */
10076 int
10077 SaveGameOldStyle(f)
10078      FILE *f;
10079 {
10080     int i, offset;
10081     time_t tm;
10082     
10083     tm = time((time_t *) NULL);
10084     
10085     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10086     PrintOpponents(f);
10087     
10088     if (backwardMostMove > 0 || startedFromSetupPosition) {
10089         fprintf(f, "\n[--------------\n");
10090         PrintPosition(f, backwardMostMove);
10091         fprintf(f, "--------------]\n");
10092     } else {
10093         fprintf(f, "\n");
10094     }
10095
10096     i = backwardMostMove;
10097     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10098
10099     while (i < forwardMostMove) {
10100         if (commentList[i] != NULL) {
10101             fprintf(f, "[%s]\n", commentList[i]);
10102         }
10103
10104         if ((i % 2) == 1) {
10105             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10106             i++;
10107         } else {
10108             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10109             i++;
10110             if (commentList[i] != NULL) {
10111                 fprintf(f, "\n");
10112                 continue;
10113             }
10114             if (i >= forwardMostMove) {
10115                 fprintf(f, "\n");
10116                 break;
10117             }
10118             fprintf(f, "%s\n", parseList[i]);
10119             i++;
10120         }
10121     }
10122     
10123     if (commentList[i] != NULL) {
10124         fprintf(f, "[%s]\n", commentList[i]);
10125     }
10126
10127     /* This isn't really the old style, but it's close enough */
10128     if (gameInfo.resultDetails != NULL &&
10129         gameInfo.resultDetails[0] != NULLCHAR) {
10130         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10131                 gameInfo.resultDetails);
10132     } else {
10133         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10134     }
10135
10136     fclose(f);
10137     return TRUE;
10138 }
10139
10140 /* Save the current game to open file f and close the file */
10141 int
10142 SaveGame(f, dummy, dummy2)
10143      FILE *f;
10144      int dummy;
10145      char *dummy2;
10146 {
10147     if (gameMode == EditPosition) EditPositionDone(TRUE);
10148     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10149     if (appData.oldSaveStyle)
10150       return SaveGameOldStyle(f);
10151     else
10152       return SaveGamePGN(f);
10153 }
10154
10155 /* Save the current position to the given file */
10156 int
10157 SavePositionToFile(filename)
10158      char *filename;
10159 {
10160     FILE *f;
10161     char buf[MSG_SIZ];
10162
10163     if (strcmp(filename, "-") == 0) {
10164         return SavePosition(stdout, 0, NULL);
10165     } else {
10166         f = fopen(filename, "a");
10167         if (f == NULL) {
10168             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10169             DisplayError(buf, errno);
10170             return FALSE;
10171         } else {
10172             SavePosition(f, 0, NULL);
10173             return TRUE;
10174         }
10175     }
10176 }
10177
10178 /* Save the current position to the given open file and close the file */
10179 int
10180 SavePosition(f, dummy, dummy2)
10181      FILE *f;
10182      int dummy;
10183      char *dummy2;
10184 {
10185     time_t tm;
10186     char *fen;
10187     
10188     if (gameMode == EditPosition) EditPositionDone(TRUE);
10189     if (appData.oldSaveStyle) {
10190         tm = time((time_t *) NULL);
10191     
10192         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10193         PrintOpponents(f);
10194         fprintf(f, "[--------------\n");
10195         PrintPosition(f, currentMove);
10196         fprintf(f, "--------------]\n");
10197     } else {
10198         fen = PositionToFEN(currentMove, NULL);
10199         fprintf(f, "%s\n", fen);
10200         free(fen);
10201     }
10202     fclose(f);
10203     return TRUE;
10204 }
10205
10206 void
10207 ReloadCmailMsgEvent(unregister)
10208      int unregister;
10209 {
10210 #if !WIN32
10211     static char *inFilename = NULL;
10212     static char *outFilename;
10213     int i;
10214     struct stat inbuf, outbuf;
10215     int status;
10216     
10217     /* Any registered moves are unregistered if unregister is set, */
10218     /* i.e. invoked by the signal handler */
10219     if (unregister) {
10220         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10221             cmailMoveRegistered[i] = FALSE;
10222             if (cmailCommentList[i] != NULL) {
10223                 free(cmailCommentList[i]);
10224                 cmailCommentList[i] = NULL;
10225             }
10226         }
10227         nCmailMovesRegistered = 0;
10228     }
10229
10230     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10231         cmailResult[i] = CMAIL_NOT_RESULT;
10232     }
10233     nCmailResults = 0;
10234
10235     if (inFilename == NULL) {
10236         /* Because the filenames are static they only get malloced once  */
10237         /* and they never get freed                                      */
10238         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10239         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10240
10241         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10242         sprintf(outFilename, "%s.out", appData.cmailGameName);
10243     }
10244     
10245     status = stat(outFilename, &outbuf);
10246     if (status < 0) {
10247         cmailMailedMove = FALSE;
10248     } else {
10249         status = stat(inFilename, &inbuf);
10250         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10251     }
10252     
10253     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10254        counts the games, notes how each one terminated, etc.
10255        
10256        It would be nice to remove this kludge and instead gather all
10257        the information while building the game list.  (And to keep it
10258        in the game list nodes instead of having a bunch of fixed-size
10259        parallel arrays.)  Note this will require getting each game's
10260        termination from the PGN tags, as the game list builder does
10261        not process the game moves.  --mann
10262        */
10263     cmailMsgLoaded = TRUE;
10264     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10265     
10266     /* Load first game in the file or popup game menu */
10267     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10268
10269 #endif /* !WIN32 */
10270     return;
10271 }
10272
10273 int
10274 RegisterMove()
10275 {
10276     FILE *f;
10277     char string[MSG_SIZ];
10278
10279     if (   cmailMailedMove
10280         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10281         return TRUE;            /* Allow free viewing  */
10282     }
10283
10284     /* Unregister move to ensure that we don't leave RegisterMove        */
10285     /* with the move registered when the conditions for registering no   */
10286     /* longer hold                                                       */
10287     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10288         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10289         nCmailMovesRegistered --;
10290
10291         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10292           {
10293               free(cmailCommentList[lastLoadGameNumber - 1]);
10294               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10295           }
10296     }
10297
10298     if (cmailOldMove == -1) {
10299         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10300         return FALSE;
10301     }
10302
10303     if (currentMove > cmailOldMove + 1) {
10304         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10305         return FALSE;
10306     }
10307
10308     if (currentMove < cmailOldMove) {
10309         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10310         return FALSE;
10311     }
10312
10313     if (forwardMostMove > currentMove) {
10314         /* Silently truncate extra moves */
10315         TruncateGame();
10316     }
10317
10318     if (   (currentMove == cmailOldMove + 1)
10319         || (   (currentMove == cmailOldMove)
10320             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10321                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10322         if (gameInfo.result != GameUnfinished) {
10323             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10324         }
10325
10326         if (commentList[currentMove] != NULL) {
10327             cmailCommentList[lastLoadGameNumber - 1]
10328               = StrSave(commentList[currentMove]);
10329         }
10330         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10331
10332         if (appData.debugMode)
10333           fprintf(debugFP, "Saving %s for game %d\n",
10334                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10335
10336         sprintf(string,
10337                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10338         
10339         f = fopen(string, "w");
10340         if (appData.oldSaveStyle) {
10341             SaveGameOldStyle(f); /* also closes the file */
10342             
10343             sprintf(string, "%s.pos.out", appData.cmailGameName);
10344             f = fopen(string, "w");
10345             SavePosition(f, 0, NULL); /* also closes the file */
10346         } else {
10347             fprintf(f, "{--------------\n");
10348             PrintPosition(f, currentMove);
10349             fprintf(f, "--------------}\n\n");
10350             
10351             SaveGame(f, 0, NULL); /* also closes the file*/
10352         }
10353         
10354         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10355         nCmailMovesRegistered ++;
10356     } else if (nCmailGames == 1) {
10357         DisplayError(_("You have not made a move yet"), 0);
10358         return FALSE;
10359     }
10360
10361     return TRUE;
10362 }
10363
10364 void
10365 MailMoveEvent()
10366 {
10367 #if !WIN32
10368     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10369     FILE *commandOutput;
10370     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10371     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10372     int nBuffers;
10373     int i;
10374     int archived;
10375     char *arcDir;
10376
10377     if (! cmailMsgLoaded) {
10378         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10379         return;
10380     }
10381
10382     if (nCmailGames == nCmailResults) {
10383         DisplayError(_("No unfinished games"), 0);
10384         return;
10385     }
10386
10387 #if CMAIL_PROHIBIT_REMAIL
10388     if (cmailMailedMove) {
10389         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);
10390         DisplayError(msg, 0);
10391         return;
10392     }
10393 #endif
10394
10395     if (! (cmailMailedMove || RegisterMove())) return;
10396     
10397     if (   cmailMailedMove
10398         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10399         sprintf(string, partCommandString,
10400                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10401         commandOutput = popen(string, "r");
10402
10403         if (commandOutput == NULL) {
10404             DisplayError(_("Failed to invoke cmail"), 0);
10405         } else {
10406             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10407                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10408             }
10409             if (nBuffers > 1) {
10410                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10411                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10412                 nBytes = MSG_SIZ - 1;
10413             } else {
10414                 (void) memcpy(msg, buffer, nBytes);
10415             }
10416             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10417
10418             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10419                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10420
10421                 archived = TRUE;
10422                 for (i = 0; i < nCmailGames; i ++) {
10423                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10424                         archived = FALSE;
10425                     }
10426                 }
10427                 if (   archived
10428                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10429                         != NULL)) {
10430                     sprintf(buffer, "%s/%s.%s.archive",
10431                             arcDir,
10432                             appData.cmailGameName,
10433                             gameInfo.date);
10434                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10435                     cmailMsgLoaded = FALSE;
10436                 }
10437             }
10438
10439             DisplayInformation(msg);
10440             pclose(commandOutput);
10441         }
10442     } else {
10443         if ((*cmailMsg) != '\0') {
10444             DisplayInformation(cmailMsg);
10445         }
10446     }
10447
10448     return;
10449 #endif /* !WIN32 */
10450 }
10451
10452 char *
10453 CmailMsg()
10454 {
10455 #if WIN32
10456     return NULL;
10457 #else
10458     int  prependComma = 0;
10459     char number[5];
10460     char string[MSG_SIZ];       /* Space for game-list */
10461     int  i;
10462     
10463     if (!cmailMsgLoaded) return "";
10464
10465     if (cmailMailedMove) {
10466         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10467     } else {
10468         /* Create a list of games left */
10469         sprintf(string, "[");
10470         for (i = 0; i < nCmailGames; i ++) {
10471             if (! (   cmailMoveRegistered[i]
10472                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10473                 if (prependComma) {
10474                     sprintf(number, ",%d", i + 1);
10475                 } else {
10476                     sprintf(number, "%d", i + 1);
10477                     prependComma = 1;
10478                 }
10479                 
10480                 strcat(string, number);
10481             }
10482         }
10483         strcat(string, "]");
10484
10485         if (nCmailMovesRegistered + nCmailResults == 0) {
10486             switch (nCmailGames) {
10487               case 1:
10488                 sprintf(cmailMsg,
10489                         _("Still need to make move for game\n"));
10490                 break;
10491                 
10492               case 2:
10493                 sprintf(cmailMsg,
10494                         _("Still need to make moves for both games\n"));
10495                 break;
10496                 
10497               default:
10498                 sprintf(cmailMsg,
10499                         _("Still need to make moves for all %d games\n"),
10500                         nCmailGames);
10501                 break;
10502             }
10503         } else {
10504             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10505               case 1:
10506                 sprintf(cmailMsg,
10507                         _("Still need to make a move for game %s\n"),
10508                         string);
10509                 break;
10510                 
10511               case 0:
10512                 if (nCmailResults == nCmailGames) {
10513                     sprintf(cmailMsg, _("No unfinished games\n"));
10514                 } else {
10515                     sprintf(cmailMsg, _("Ready to send mail\n"));
10516                 }
10517                 break;
10518                 
10519               default:
10520                 sprintf(cmailMsg,
10521                         _("Still need to make moves for games %s\n"),
10522                         string);
10523             }
10524         }
10525     }
10526     return cmailMsg;
10527 #endif /* WIN32 */
10528 }
10529
10530 void
10531 ResetGameEvent()
10532 {
10533     if (gameMode == Training)
10534       SetTrainingModeOff();
10535
10536     Reset(TRUE, TRUE);
10537     cmailMsgLoaded = FALSE;
10538     if (appData.icsActive) {
10539       SendToICS(ics_prefix);
10540       SendToICS("refresh\n");
10541     }
10542 }
10543
10544 void
10545 ExitEvent(status)
10546      int status;
10547 {
10548     exiting++;
10549     if (exiting > 2) {
10550       /* Give up on clean exit */
10551       exit(status);
10552     }
10553     if (exiting > 1) {
10554       /* Keep trying for clean exit */
10555       return;
10556     }
10557
10558     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10559
10560     if (telnetISR != NULL) {
10561       RemoveInputSource(telnetISR);
10562     }
10563     if (icsPR != NoProc) {
10564       DestroyChildProcess(icsPR, TRUE);
10565     }
10566
10567     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10568     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10569
10570     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10571     /* make sure this other one finishes before killing it!                  */
10572     if(endingGame) { int count = 0;
10573         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10574         while(endingGame && count++ < 10) DoSleep(1);
10575         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10576     }
10577
10578     /* Kill off chess programs */
10579     if (first.pr != NoProc) {
10580         ExitAnalyzeMode();
10581         
10582         DoSleep( appData.delayBeforeQuit );
10583         SendToProgram("quit\n", &first);
10584         DoSleep( appData.delayAfterQuit );
10585         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10586     }
10587     if (second.pr != NoProc) {
10588         DoSleep( appData.delayBeforeQuit );
10589         SendToProgram("quit\n", &second);
10590         DoSleep( appData.delayAfterQuit );
10591         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10592     }
10593     if (first.isr != NULL) {
10594         RemoveInputSource(first.isr);
10595     }
10596     if (second.isr != NULL) {
10597         RemoveInputSource(second.isr);
10598     }
10599
10600     ShutDownFrontEnd();
10601     exit(status);
10602 }
10603
10604 void
10605 PauseEvent()
10606 {
10607     if (appData.debugMode)
10608         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10609     if (pausing) {
10610         pausing = FALSE;
10611         ModeHighlight();
10612         if (gameMode == MachinePlaysWhite ||
10613             gameMode == MachinePlaysBlack) {
10614             StartClocks();
10615         } else {
10616             DisplayBothClocks();
10617         }
10618         if (gameMode == PlayFromGameFile) {
10619             if (appData.timeDelay >= 0) 
10620                 AutoPlayGameLoop();
10621         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10622             Reset(FALSE, TRUE);
10623             SendToICS(ics_prefix);
10624             SendToICS("refresh\n");
10625         } else if (currentMove < forwardMostMove) {
10626             ForwardInner(forwardMostMove);
10627         }
10628         pauseExamInvalid = FALSE;
10629     } else {
10630         switch (gameMode) {
10631           default:
10632             return;
10633           case IcsExamining:
10634             pauseExamForwardMostMove = forwardMostMove;
10635             pauseExamInvalid = FALSE;
10636             /* fall through */
10637           case IcsObserving:
10638           case IcsPlayingWhite:
10639           case IcsPlayingBlack:
10640             pausing = TRUE;
10641             ModeHighlight();
10642             return;
10643           case PlayFromGameFile:
10644             (void) StopLoadGameTimer();
10645             pausing = TRUE;
10646             ModeHighlight();
10647             break;
10648           case BeginningOfGame:
10649             if (appData.icsActive) return;
10650             /* else fall through */
10651           case MachinePlaysWhite:
10652           case MachinePlaysBlack:
10653           case TwoMachinesPlay:
10654             if (forwardMostMove == 0)
10655               return;           /* don't pause if no one has moved */
10656             if ((gameMode == MachinePlaysWhite &&
10657                  !WhiteOnMove(forwardMostMove)) ||
10658                 (gameMode == MachinePlaysBlack &&
10659                  WhiteOnMove(forwardMostMove))) {
10660                 StopClocks();
10661             }
10662             pausing = TRUE;
10663             ModeHighlight();
10664             break;
10665         }
10666     }
10667 }
10668
10669 void
10670 EditCommentEvent()
10671 {
10672     char title[MSG_SIZ];
10673
10674     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10675         strcpy(title, _("Edit comment"));
10676     } else {
10677         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10678                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10679                 parseList[currentMove - 1]);
10680     }
10681
10682     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10683 }
10684
10685
10686 void
10687 EditTagsEvent()
10688 {
10689     char *tags = PGNTags(&gameInfo);
10690     EditTagsPopUp(tags);
10691     free(tags);
10692 }
10693
10694 void
10695 AnalyzeModeEvent()
10696 {
10697     if (appData.noChessProgram || gameMode == AnalyzeMode)
10698       return;
10699
10700     if (gameMode != AnalyzeFile) {
10701         if (!appData.icsEngineAnalyze) {
10702                EditGameEvent();
10703                if (gameMode != EditGame) return;
10704         }
10705         ResurrectChessProgram();
10706         SendToProgram("analyze\n", &first);
10707         first.analyzing = TRUE;
10708         /*first.maybeThinking = TRUE;*/
10709         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10710         EngineOutputPopUp();
10711     }
10712     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10713     pausing = FALSE;
10714     ModeHighlight();
10715     SetGameInfo();
10716
10717     StartAnalysisClock();
10718     GetTimeMark(&lastNodeCountTime);
10719     lastNodeCount = 0;
10720 }
10721
10722 void
10723 AnalyzeFileEvent()
10724 {
10725     if (appData.noChessProgram || gameMode == AnalyzeFile)
10726       return;
10727
10728     if (gameMode != AnalyzeMode) {
10729         EditGameEvent();
10730         if (gameMode != EditGame) return;
10731         ResurrectChessProgram();
10732         SendToProgram("analyze\n", &first);
10733         first.analyzing = TRUE;
10734         /*first.maybeThinking = TRUE;*/
10735         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10736         EngineOutputPopUp();
10737     }
10738     gameMode = AnalyzeFile;
10739     pausing = FALSE;
10740     ModeHighlight();
10741     SetGameInfo();
10742
10743     StartAnalysisClock();
10744     GetTimeMark(&lastNodeCountTime);
10745     lastNodeCount = 0;
10746 }
10747
10748 void
10749 MachineWhiteEvent()
10750 {
10751     char buf[MSG_SIZ];
10752     char *bookHit = NULL;
10753
10754     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10755       return;
10756
10757
10758     if (gameMode == PlayFromGameFile || 
10759         gameMode == TwoMachinesPlay  || 
10760         gameMode == Training         || 
10761         gameMode == AnalyzeMode      || 
10762         gameMode == EndOfGame)
10763         EditGameEvent();
10764
10765     if (gameMode == EditPosition) 
10766         EditPositionDone(TRUE);
10767
10768     if (!WhiteOnMove(currentMove)) {
10769         DisplayError(_("It is not White's turn"), 0);
10770         return;
10771     }
10772   
10773     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10774       ExitAnalyzeMode();
10775
10776     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10777         gameMode == AnalyzeFile)
10778         TruncateGame();
10779
10780     ResurrectChessProgram();    /* in case it isn't running */
10781     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10782         gameMode = MachinePlaysWhite;
10783         ResetClocks();
10784     } else
10785     gameMode = MachinePlaysWhite;
10786     pausing = FALSE;
10787     ModeHighlight();
10788     SetGameInfo();
10789     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10790     DisplayTitle(buf);
10791     if (first.sendName) {
10792       sprintf(buf, "name %s\n", gameInfo.black);
10793       SendToProgram(buf, &first);
10794     }
10795     if (first.sendTime) {
10796       if (first.useColors) {
10797         SendToProgram("black\n", &first); /*gnu kludge*/
10798       }
10799       SendTimeRemaining(&first, TRUE);
10800     }
10801     if (first.useColors) {
10802       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10803     }
10804     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10805     SetMachineThinkingEnables();
10806     first.maybeThinking = TRUE;
10807     StartClocks();
10808     firstMove = FALSE;
10809
10810     if (appData.autoFlipView && !flipView) {
10811       flipView = !flipView;
10812       DrawPosition(FALSE, NULL);
10813       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10814     }
10815
10816     if(bookHit) { // [HGM] book: simulate book reply
10817         static char bookMove[MSG_SIZ]; // a bit generous?
10818
10819         programStats.nodes = programStats.depth = programStats.time = 
10820         programStats.score = programStats.got_only_move = 0;
10821         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10822
10823         strcpy(bookMove, "move ");
10824         strcat(bookMove, bookHit);
10825         HandleMachineMove(bookMove, &first);
10826     }
10827 }
10828
10829 void
10830 MachineBlackEvent()
10831 {
10832     char buf[MSG_SIZ];
10833    char *bookHit = NULL;
10834
10835     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10836         return;
10837
10838
10839     if (gameMode == PlayFromGameFile || 
10840         gameMode == TwoMachinesPlay  || 
10841         gameMode == Training         || 
10842         gameMode == AnalyzeMode      || 
10843         gameMode == EndOfGame)
10844         EditGameEvent();
10845
10846     if (gameMode == EditPosition) 
10847         EditPositionDone(TRUE);
10848
10849     if (WhiteOnMove(currentMove)) {
10850         DisplayError(_("It is not Black's turn"), 0);
10851         return;
10852     }
10853     
10854     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10855       ExitAnalyzeMode();
10856
10857     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10858         gameMode == AnalyzeFile)
10859         TruncateGame();
10860
10861     ResurrectChessProgram();    /* in case it isn't running */
10862     gameMode = MachinePlaysBlack;
10863     pausing = FALSE;
10864     ModeHighlight();
10865     SetGameInfo();
10866     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10867     DisplayTitle(buf);
10868     if (first.sendName) {
10869       sprintf(buf, "name %s\n", gameInfo.white);
10870       SendToProgram(buf, &first);
10871     }
10872     if (first.sendTime) {
10873       if (first.useColors) {
10874         SendToProgram("white\n", &first); /*gnu kludge*/
10875       }
10876       SendTimeRemaining(&first, FALSE);
10877     }
10878     if (first.useColors) {
10879       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10880     }
10881     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10882     SetMachineThinkingEnables();
10883     first.maybeThinking = TRUE;
10884     StartClocks();
10885
10886     if (appData.autoFlipView && flipView) {
10887       flipView = !flipView;
10888       DrawPosition(FALSE, NULL);
10889       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10890     }
10891     if(bookHit) { // [HGM] book: simulate book reply
10892         static char bookMove[MSG_SIZ]; // a bit generous?
10893
10894         programStats.nodes = programStats.depth = programStats.time = 
10895         programStats.score = programStats.got_only_move = 0;
10896         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10897
10898         strcpy(bookMove, "move ");
10899         strcat(bookMove, bookHit);
10900         HandleMachineMove(bookMove, &first);
10901     }
10902 }
10903
10904
10905 void
10906 DisplayTwoMachinesTitle()
10907 {
10908     char buf[MSG_SIZ];
10909     if (appData.matchGames > 0) {
10910         if (first.twoMachinesColor[0] == 'w') {
10911             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10912                     gameInfo.white, gameInfo.black,
10913                     first.matchWins, second.matchWins,
10914                     matchGame - 1 - (first.matchWins + second.matchWins));
10915         } else {
10916             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10917                     gameInfo.white, gameInfo.black,
10918                     second.matchWins, first.matchWins,
10919                     matchGame - 1 - (first.matchWins + second.matchWins));
10920         }
10921     } else {
10922         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10923     }
10924     DisplayTitle(buf);
10925 }
10926
10927 void
10928 TwoMachinesEvent P((void))
10929 {
10930     int i;
10931     char buf[MSG_SIZ];
10932     ChessProgramState *onmove;
10933     char *bookHit = NULL;
10934     
10935     if (appData.noChessProgram) return;
10936
10937     switch (gameMode) {
10938       case TwoMachinesPlay:
10939         return;
10940       case MachinePlaysWhite:
10941       case MachinePlaysBlack:
10942         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10943             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10944             return;
10945         }
10946         /* fall through */
10947       case BeginningOfGame:
10948       case PlayFromGameFile:
10949       case EndOfGame:
10950         EditGameEvent();
10951         if (gameMode != EditGame) return;
10952         break;
10953       case EditPosition:
10954         EditPositionDone(TRUE);
10955         break;
10956       case AnalyzeMode:
10957       case AnalyzeFile:
10958         ExitAnalyzeMode();
10959         break;
10960       case EditGame:
10961       default:
10962         break;
10963     }
10964
10965 //    forwardMostMove = currentMove;
10966     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10967     ResurrectChessProgram();    /* in case first program isn't running */
10968
10969     if (second.pr == NULL) {
10970         StartChessProgram(&second);
10971         if (second.protocolVersion == 1) {
10972           TwoMachinesEventIfReady();
10973         } else {
10974           /* kludge: allow timeout for initial "feature" command */
10975           FreezeUI();
10976           DisplayMessage("", _("Starting second chess program"));
10977           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10978         }
10979         return;
10980     }
10981     DisplayMessage("", "");
10982     InitChessProgram(&second, FALSE);
10983     SendToProgram("force\n", &second);
10984     if (startedFromSetupPosition) {
10985         SendBoard(&second, backwardMostMove);
10986     if (appData.debugMode) {
10987         fprintf(debugFP, "Two Machines\n");
10988     }
10989     }
10990     for (i = backwardMostMove; i < forwardMostMove; i++) {
10991         SendMoveToProgram(i, &second);
10992     }
10993
10994     gameMode = TwoMachinesPlay;
10995     pausing = FALSE;
10996     ModeHighlight();
10997     SetGameInfo();
10998     DisplayTwoMachinesTitle();
10999     firstMove = TRUE;
11000     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11001         onmove = &first;
11002     } else {
11003         onmove = &second;
11004     }
11005
11006     SendToProgram(first.computerString, &first);
11007     if (first.sendName) {
11008       sprintf(buf, "name %s\n", second.tidy);
11009       SendToProgram(buf, &first);
11010     }
11011     SendToProgram(second.computerString, &second);
11012     if (second.sendName) {
11013       sprintf(buf, "name %s\n", first.tidy);
11014       SendToProgram(buf, &second);
11015     }
11016
11017     ResetClocks();
11018     if (!first.sendTime || !second.sendTime) {
11019         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11020         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11021     }
11022     if (onmove->sendTime) {
11023       if (onmove->useColors) {
11024         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11025       }
11026       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11027     }
11028     if (onmove->useColors) {
11029       SendToProgram(onmove->twoMachinesColor, onmove);
11030     }
11031     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11032 //    SendToProgram("go\n", onmove);
11033     onmove->maybeThinking = TRUE;
11034     SetMachineThinkingEnables();
11035
11036     StartClocks();
11037
11038     if(bookHit) { // [HGM] book: simulate book reply
11039         static char bookMove[MSG_SIZ]; // a bit generous?
11040
11041         programStats.nodes = programStats.depth = programStats.time = 
11042         programStats.score = programStats.got_only_move = 0;
11043         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11044
11045         strcpy(bookMove, "move ");
11046         strcat(bookMove, bookHit);
11047         savedMessage = bookMove; // args for deferred call
11048         savedState = onmove;
11049         ScheduleDelayedEvent(DeferredBookMove, 1);
11050     }
11051 }
11052
11053 void
11054 TrainingEvent()
11055 {
11056     if (gameMode == Training) {
11057       SetTrainingModeOff();
11058       gameMode = PlayFromGameFile;
11059       DisplayMessage("", _("Training mode off"));
11060     } else {
11061       gameMode = Training;
11062       animateTraining = appData.animate;
11063
11064       /* make sure we are not already at the end of the game */
11065       if (currentMove < forwardMostMove) {
11066         SetTrainingModeOn();
11067         DisplayMessage("", _("Training mode on"));
11068       } else {
11069         gameMode = PlayFromGameFile;
11070         DisplayError(_("Already at end of game"), 0);
11071       }
11072     }
11073     ModeHighlight();
11074 }
11075
11076 void
11077 IcsClientEvent()
11078 {
11079     if (!appData.icsActive) return;
11080     switch (gameMode) {
11081       case IcsPlayingWhite:
11082       case IcsPlayingBlack:
11083       case IcsObserving:
11084       case IcsIdle:
11085       case BeginningOfGame:
11086       case IcsExamining:
11087         return;
11088
11089       case EditGame:
11090         break;
11091
11092       case EditPosition:
11093         EditPositionDone(TRUE);
11094         break;
11095
11096       case AnalyzeMode:
11097       case AnalyzeFile:
11098         ExitAnalyzeMode();
11099         break;
11100         
11101       default:
11102         EditGameEvent();
11103         break;
11104     }
11105
11106     gameMode = IcsIdle;
11107     ModeHighlight();
11108     return;
11109 }
11110
11111
11112 void
11113 EditGameEvent()
11114 {
11115     int i;
11116
11117     switch (gameMode) {
11118       case Training:
11119         SetTrainingModeOff();
11120         break;
11121       case MachinePlaysWhite:
11122       case MachinePlaysBlack:
11123       case BeginningOfGame:
11124         SendToProgram("force\n", &first);
11125         SetUserThinkingEnables();
11126         break;
11127       case PlayFromGameFile:
11128         (void) StopLoadGameTimer();
11129         if (gameFileFP != NULL) {
11130             gameFileFP = NULL;
11131         }
11132         break;
11133       case EditPosition:
11134         EditPositionDone(TRUE);
11135         break;
11136       case AnalyzeMode:
11137       case AnalyzeFile:
11138         ExitAnalyzeMode();
11139         SendToProgram("force\n", &first);
11140         break;
11141       case TwoMachinesPlay:
11142         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11143         ResurrectChessProgram();
11144         SetUserThinkingEnables();
11145         break;
11146       case EndOfGame:
11147         ResurrectChessProgram();
11148         break;
11149       case IcsPlayingBlack:
11150       case IcsPlayingWhite:
11151         DisplayError(_("Warning: You are still playing a game"), 0);
11152         break;
11153       case IcsObserving:
11154         DisplayError(_("Warning: You are still observing a game"), 0);
11155         break;
11156       case IcsExamining:
11157         DisplayError(_("Warning: You are still examining a game"), 0);
11158         break;
11159       case IcsIdle:
11160         break;
11161       case EditGame:
11162       default:
11163         return;
11164     }
11165     
11166     pausing = FALSE;
11167     StopClocks();
11168     first.offeredDraw = second.offeredDraw = 0;
11169
11170     if (gameMode == PlayFromGameFile) {
11171         whiteTimeRemaining = timeRemaining[0][currentMove];
11172         blackTimeRemaining = timeRemaining[1][currentMove];
11173         DisplayTitle("");
11174     }
11175
11176     if (gameMode == MachinePlaysWhite ||
11177         gameMode == MachinePlaysBlack ||
11178         gameMode == TwoMachinesPlay ||
11179         gameMode == EndOfGame) {
11180         i = forwardMostMove;
11181         while (i > currentMove) {
11182             SendToProgram("undo\n", &first);
11183             i--;
11184         }
11185         whiteTimeRemaining = timeRemaining[0][currentMove];
11186         blackTimeRemaining = timeRemaining[1][currentMove];
11187         DisplayBothClocks();
11188         if (whiteFlag || blackFlag) {
11189             whiteFlag = blackFlag = 0;
11190         }
11191         DisplayTitle("");
11192     }           
11193     
11194     gameMode = EditGame;
11195     ModeHighlight();
11196     SetGameInfo();
11197 }
11198
11199
11200 void
11201 EditPositionEvent()
11202 {
11203     if (gameMode == EditPosition) {
11204         EditGameEvent();
11205         return;
11206     }
11207     
11208     EditGameEvent();
11209     if (gameMode != EditGame) return;
11210     
11211     gameMode = EditPosition;
11212     ModeHighlight();
11213     SetGameInfo();
11214     if (currentMove > 0)
11215       CopyBoard(boards[0], boards[currentMove]);
11216     
11217     blackPlaysFirst = !WhiteOnMove(currentMove);
11218     ResetClocks();
11219     currentMove = forwardMostMove = backwardMostMove = 0;
11220     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11221     DisplayMove(-1);
11222 }
11223
11224 void
11225 ExitAnalyzeMode()
11226 {
11227     /* [DM] icsEngineAnalyze - possible call from other functions */
11228     if (appData.icsEngineAnalyze) {
11229         appData.icsEngineAnalyze = FALSE;
11230
11231         DisplayMessage("",_("Close ICS engine analyze..."));
11232     }
11233     if (first.analysisSupport && first.analyzing) {
11234       SendToProgram("exit\n", &first);
11235       first.analyzing = FALSE;
11236     }
11237     thinkOutput[0] = NULLCHAR;
11238 }
11239
11240 void
11241 EditPositionDone(Boolean fakeRights)
11242 {
11243     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11244
11245     startedFromSetupPosition = TRUE;
11246     InitChessProgram(&first, FALSE);
11247     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11248       boards[0][EP_STATUS] = EP_NONE;
11249       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11250     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11251         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11252         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11253       } else boards[0][CASTLING][2] = NoRights;
11254     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11255         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11256         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11257       } else boards[0][CASTLING][5] = NoRights;
11258     }
11259     SendToProgram("force\n", &first);
11260     if (blackPlaysFirst) {
11261         strcpy(moveList[0], "");
11262         strcpy(parseList[0], "");
11263         currentMove = forwardMostMove = backwardMostMove = 1;
11264         CopyBoard(boards[1], boards[0]);
11265     } else {
11266         currentMove = forwardMostMove = backwardMostMove = 0;
11267     }
11268     SendBoard(&first, forwardMostMove);
11269     if (appData.debugMode) {
11270         fprintf(debugFP, "EditPosDone\n");
11271     }
11272     DisplayTitle("");
11273     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11274     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11275     gameMode = EditGame;
11276     ModeHighlight();
11277     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11278     ClearHighlights(); /* [AS] */
11279 }
11280
11281 /* Pause for `ms' milliseconds */
11282 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11283 void
11284 TimeDelay(ms)
11285      long ms;
11286 {
11287     TimeMark m1, m2;
11288
11289     GetTimeMark(&m1);
11290     do {
11291         GetTimeMark(&m2);
11292     } while (SubtractTimeMarks(&m2, &m1) < ms);
11293 }
11294
11295 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11296 void
11297 SendMultiLineToICS(buf)
11298      char *buf;
11299 {
11300     char temp[MSG_SIZ+1], *p;
11301     int len;
11302
11303     len = strlen(buf);
11304     if (len > MSG_SIZ)
11305       len = MSG_SIZ;
11306   
11307     strncpy(temp, buf, len);
11308     temp[len] = 0;
11309
11310     p = temp;
11311     while (*p) {
11312         if (*p == '\n' || *p == '\r')
11313           *p = ' ';
11314         ++p;
11315     }
11316
11317     strcat(temp, "\n");
11318     SendToICS(temp);
11319     SendToPlayer(temp, strlen(temp));
11320 }
11321
11322 void
11323 SetWhiteToPlayEvent()
11324 {
11325     if (gameMode == EditPosition) {
11326         blackPlaysFirst = FALSE;
11327         DisplayBothClocks();    /* works because currentMove is 0 */
11328     } else if (gameMode == IcsExamining) {
11329         SendToICS(ics_prefix);
11330         SendToICS("tomove white\n");
11331     }
11332 }
11333
11334 void
11335 SetBlackToPlayEvent()
11336 {
11337     if (gameMode == EditPosition) {
11338         blackPlaysFirst = TRUE;
11339         currentMove = 1;        /* kludge */
11340         DisplayBothClocks();
11341         currentMove = 0;
11342     } else if (gameMode == IcsExamining) {
11343         SendToICS(ics_prefix);
11344         SendToICS("tomove black\n");
11345     }
11346 }
11347
11348 void
11349 EditPositionMenuEvent(selection, x, y)
11350      ChessSquare selection;
11351      int x, y;
11352 {
11353     char buf[MSG_SIZ];
11354     ChessSquare piece = boards[0][y][x];
11355
11356     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11357
11358     switch (selection) {
11359       case ClearBoard:
11360         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11361             SendToICS(ics_prefix);
11362             SendToICS("bsetup clear\n");
11363         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11364             SendToICS(ics_prefix);
11365             SendToICS("clearboard\n");
11366         } else {
11367             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11368                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11369                 for (y = 0; y < BOARD_HEIGHT; y++) {
11370                     if (gameMode == IcsExamining) {
11371                         if (boards[currentMove][y][x] != EmptySquare) {
11372                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11373                                     AAA + x, ONE + y);
11374                             SendToICS(buf);
11375                         }
11376                     } else {
11377                         boards[0][y][x] = p;
11378                     }
11379                 }
11380             }
11381         }
11382         if (gameMode == EditPosition) {
11383             DrawPosition(FALSE, boards[0]);
11384         }
11385         break;
11386
11387       case WhitePlay:
11388         SetWhiteToPlayEvent();
11389         break;
11390
11391       case BlackPlay:
11392         SetBlackToPlayEvent();
11393         break;
11394
11395       case EmptySquare:
11396         if (gameMode == IcsExamining) {
11397             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11398             SendToICS(buf);
11399         } else {
11400             boards[0][y][x] = EmptySquare;
11401             DrawPosition(FALSE, boards[0]);
11402         }
11403         break;
11404
11405       case PromotePiece:
11406         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11407            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11408             selection = (ChessSquare) (PROMOTED piece);
11409         } else if(piece == EmptySquare) selection = WhiteSilver;
11410         else selection = (ChessSquare)((int)piece - 1);
11411         goto defaultlabel;
11412
11413       case DemotePiece:
11414         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11415            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11416             selection = (ChessSquare) (DEMOTED piece);
11417         } else if(piece == EmptySquare) selection = BlackSilver;
11418         else selection = (ChessSquare)((int)piece + 1);       
11419         goto defaultlabel;
11420
11421       case WhiteQueen:
11422       case BlackQueen:
11423         if(gameInfo.variant == VariantShatranj ||
11424            gameInfo.variant == VariantXiangqi  ||
11425            gameInfo.variant == VariantCourier    )
11426             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11427         goto defaultlabel;
11428
11429       case WhiteKing:
11430       case BlackKing:
11431         if(gameInfo.variant == VariantXiangqi)
11432             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11433         if(gameInfo.variant == VariantKnightmate)
11434             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11435       default:
11436         defaultlabel:
11437         if (gameMode == IcsExamining) {
11438             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11439                     PieceToChar(selection), AAA + x, ONE + y);
11440             SendToICS(buf);
11441         } else {
11442             boards[0][y][x] = selection;
11443             DrawPosition(FALSE, boards[0]);
11444         }
11445         break;
11446     }
11447 }
11448
11449
11450 void
11451 DropMenuEvent(selection, x, y)
11452      ChessSquare selection;
11453      int x, y;
11454 {
11455     ChessMove moveType;
11456
11457     switch (gameMode) {
11458       case IcsPlayingWhite:
11459       case MachinePlaysBlack:
11460         if (!WhiteOnMove(currentMove)) {
11461             DisplayMoveError(_("It is Black's turn"));
11462             return;
11463         }
11464         moveType = WhiteDrop;
11465         break;
11466       case IcsPlayingBlack:
11467       case MachinePlaysWhite:
11468         if (WhiteOnMove(currentMove)) {
11469             DisplayMoveError(_("It is White's turn"));
11470             return;
11471         }
11472         moveType = BlackDrop;
11473         break;
11474       case EditGame:
11475         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11476         break;
11477       default:
11478         return;
11479     }
11480
11481     if (moveType == BlackDrop && selection < BlackPawn) {
11482       selection = (ChessSquare) ((int) selection
11483                                  + (int) BlackPawn - (int) WhitePawn);
11484     }
11485     if (boards[currentMove][y][x] != EmptySquare) {
11486         DisplayMoveError(_("That square is occupied"));
11487         return;
11488     }
11489
11490     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11491 }
11492
11493 void
11494 AcceptEvent()
11495 {
11496     /* Accept a pending offer of any kind from opponent */
11497     
11498     if (appData.icsActive) {
11499         SendToICS(ics_prefix);
11500         SendToICS("accept\n");
11501     } else if (cmailMsgLoaded) {
11502         if (currentMove == cmailOldMove &&
11503             commentList[cmailOldMove] != NULL &&
11504             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11505                    "Black offers a draw" : "White offers a draw")) {
11506             TruncateGame();
11507             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11508             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11509         } else {
11510             DisplayError(_("There is no pending offer on this move"), 0);
11511             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11512         }
11513     } else {
11514         /* Not used for offers from chess program */
11515     }
11516 }
11517
11518 void
11519 DeclineEvent()
11520 {
11521     /* Decline a pending offer of any kind from opponent */
11522     
11523     if (appData.icsActive) {
11524         SendToICS(ics_prefix);
11525         SendToICS("decline\n");
11526     } else if (cmailMsgLoaded) {
11527         if (currentMove == cmailOldMove &&
11528             commentList[cmailOldMove] != NULL &&
11529             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11530                    "Black offers a draw" : "White offers a draw")) {
11531 #ifdef NOTDEF
11532             AppendComment(cmailOldMove, "Draw declined", TRUE);
11533             DisplayComment(cmailOldMove - 1, "Draw declined");
11534 #endif /*NOTDEF*/
11535         } else {
11536             DisplayError(_("There is no pending offer on this move"), 0);
11537         }
11538     } else {
11539         /* Not used for offers from chess program */
11540     }
11541 }
11542
11543 void
11544 RematchEvent()
11545 {
11546     /* Issue ICS rematch command */
11547     if (appData.icsActive) {
11548         SendToICS(ics_prefix);
11549         SendToICS("rematch\n");
11550     }
11551 }
11552
11553 void
11554 CallFlagEvent()
11555 {
11556     /* Call your opponent's flag (claim a win on time) */
11557     if (appData.icsActive) {
11558         SendToICS(ics_prefix);
11559         SendToICS("flag\n");
11560     } else {
11561         switch (gameMode) {
11562           default:
11563             return;
11564           case MachinePlaysWhite:
11565             if (whiteFlag) {
11566                 if (blackFlag)
11567                   GameEnds(GameIsDrawn, "Both players ran out of time",
11568                            GE_PLAYER);
11569                 else
11570                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11571             } else {
11572                 DisplayError(_("Your opponent is not out of time"), 0);
11573             }
11574             break;
11575           case MachinePlaysBlack:
11576             if (blackFlag) {
11577                 if (whiteFlag)
11578                   GameEnds(GameIsDrawn, "Both players ran out of time",
11579                            GE_PLAYER);
11580                 else
11581                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11582             } else {
11583                 DisplayError(_("Your opponent is not out of time"), 0);
11584             }
11585             break;
11586         }
11587     }
11588 }
11589
11590 void
11591 DrawEvent()
11592 {
11593     /* Offer draw or accept pending draw offer from opponent */
11594     
11595     if (appData.icsActive) {
11596         /* Note: tournament rules require draw offers to be
11597            made after you make your move but before you punch
11598            your clock.  Currently ICS doesn't let you do that;
11599            instead, you immediately punch your clock after making
11600            a move, but you can offer a draw at any time. */
11601         
11602         SendToICS(ics_prefix);
11603         SendToICS("draw\n");
11604     } else if (cmailMsgLoaded) {
11605         if (currentMove == cmailOldMove &&
11606             commentList[cmailOldMove] != NULL &&
11607             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11608                    "Black offers a draw" : "White offers a draw")) {
11609             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11610             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11611         } else if (currentMove == cmailOldMove + 1) {
11612             char *offer = WhiteOnMove(cmailOldMove) ?
11613               "White offers a draw" : "Black offers a draw";
11614             AppendComment(currentMove, offer, TRUE);
11615             DisplayComment(currentMove - 1, offer);
11616             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11617         } else {
11618             DisplayError(_("You must make your move before offering a draw"), 0);
11619             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11620         }
11621     } else if (first.offeredDraw) {
11622         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11623     } else {
11624         if (first.sendDrawOffers) {
11625             SendToProgram("draw\n", &first);
11626             userOfferedDraw = TRUE;
11627         }
11628     }
11629 }
11630
11631 void
11632 AdjournEvent()
11633 {
11634     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11635     
11636     if (appData.icsActive) {
11637         SendToICS(ics_prefix);
11638         SendToICS("adjourn\n");
11639     } else {
11640         /* Currently GNU Chess doesn't offer or accept Adjourns */
11641     }
11642 }
11643
11644
11645 void
11646 AbortEvent()
11647 {
11648     /* Offer Abort or accept pending Abort offer from opponent */
11649     
11650     if (appData.icsActive) {
11651         SendToICS(ics_prefix);
11652         SendToICS("abort\n");
11653     } else {
11654         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11655     }
11656 }
11657
11658 void
11659 ResignEvent()
11660 {
11661     /* Resign.  You can do this even if it's not your turn. */
11662     
11663     if (appData.icsActive) {
11664         SendToICS(ics_prefix);
11665         SendToICS("resign\n");
11666     } else {
11667         switch (gameMode) {
11668           case MachinePlaysWhite:
11669             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11670             break;
11671           case MachinePlaysBlack:
11672             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11673             break;
11674           case EditGame:
11675             if (cmailMsgLoaded) {
11676                 TruncateGame();
11677                 if (WhiteOnMove(cmailOldMove)) {
11678                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11679                 } else {
11680                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11681                 }
11682                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11683             }
11684             break;
11685           default:
11686             break;
11687         }
11688     }
11689 }
11690
11691
11692 void
11693 StopObservingEvent()
11694 {
11695     /* Stop observing current games */
11696     SendToICS(ics_prefix);
11697     SendToICS("unobserve\n");
11698 }
11699
11700 void
11701 StopExaminingEvent()
11702 {
11703     /* Stop observing current game */
11704     SendToICS(ics_prefix);
11705     SendToICS("unexamine\n");
11706 }
11707
11708 void
11709 ForwardInner(target)
11710      int target;
11711 {
11712     int limit;
11713
11714     if (appData.debugMode)
11715         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11716                 target, currentMove, forwardMostMove);
11717
11718     if (gameMode == EditPosition)
11719       return;
11720
11721     if (gameMode == PlayFromGameFile && !pausing)
11722       PauseEvent();
11723     
11724     if (gameMode == IcsExamining && pausing)
11725       limit = pauseExamForwardMostMove;
11726     else
11727       limit = forwardMostMove;
11728     
11729     if (target > limit) target = limit;
11730
11731     if (target > 0 && moveList[target - 1][0]) {
11732         int fromX, fromY, toX, toY;
11733         toX = moveList[target - 1][2] - AAA;
11734         toY = moveList[target - 1][3] - ONE;
11735         if (moveList[target - 1][1] == '@') {
11736             if (appData.highlightLastMove) {
11737                 SetHighlights(-1, -1, toX, toY);
11738             }
11739         } else {
11740             fromX = moveList[target - 1][0] - AAA;
11741             fromY = moveList[target - 1][1] - ONE;
11742             if (target == currentMove + 1) {
11743                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11744             }
11745             if (appData.highlightLastMove) {
11746                 SetHighlights(fromX, fromY, toX, toY);
11747             }
11748         }
11749     }
11750     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11751         gameMode == Training || gameMode == PlayFromGameFile || 
11752         gameMode == AnalyzeFile) {
11753         while (currentMove < target) {
11754             SendMoveToProgram(currentMove++, &first);
11755         }
11756     } else {
11757         currentMove = target;
11758     }
11759     
11760     if (gameMode == EditGame || gameMode == EndOfGame) {
11761         whiteTimeRemaining = timeRemaining[0][currentMove];
11762         blackTimeRemaining = timeRemaining[1][currentMove];
11763     }
11764     DisplayBothClocks();
11765     DisplayMove(currentMove - 1);
11766     DrawPosition(FALSE, boards[currentMove]);
11767     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11768     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11769         DisplayComment(currentMove - 1, commentList[currentMove]);
11770     }
11771 }
11772
11773
11774 void
11775 ForwardEvent()
11776 {
11777     if (gameMode == IcsExamining && !pausing) {
11778         SendToICS(ics_prefix);
11779         SendToICS("forward\n");
11780     } else {
11781         ForwardInner(currentMove + 1);
11782     }
11783 }
11784
11785 void
11786 ToEndEvent()
11787 {
11788     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11789         /* to optimze, we temporarily turn off analysis mode while we feed
11790          * the remaining moves to the engine. Otherwise we get analysis output
11791          * after each move.
11792          */ 
11793         if (first.analysisSupport) {
11794           SendToProgram("exit\nforce\n", &first);
11795           first.analyzing = FALSE;
11796         }
11797     }
11798         
11799     if (gameMode == IcsExamining && !pausing) {
11800         SendToICS(ics_prefix);
11801         SendToICS("forward 999999\n");
11802     } else {
11803         ForwardInner(forwardMostMove);
11804     }
11805
11806     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11807         /* we have fed all the moves, so reactivate analysis mode */
11808         SendToProgram("analyze\n", &first);
11809         first.analyzing = TRUE;
11810         /*first.maybeThinking = TRUE;*/
11811         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11812     }
11813 }
11814
11815 void
11816 BackwardInner(target)
11817      int target;
11818 {
11819     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11820
11821     if (appData.debugMode)
11822         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11823                 target, currentMove, forwardMostMove);
11824
11825     if (gameMode == EditPosition) return;
11826     if (currentMove <= backwardMostMove) {
11827         ClearHighlights();
11828         DrawPosition(full_redraw, boards[currentMove]);
11829         return;
11830     }
11831     if (gameMode == PlayFromGameFile && !pausing)
11832       PauseEvent();
11833     
11834     if (moveList[target][0]) {
11835         int fromX, fromY, toX, toY;
11836         toX = moveList[target][2] - AAA;
11837         toY = moveList[target][3] - ONE;
11838         if (moveList[target][1] == '@') {
11839             if (appData.highlightLastMove) {
11840                 SetHighlights(-1, -1, toX, toY);
11841             }
11842         } else {
11843             fromX = moveList[target][0] - AAA;
11844             fromY = moveList[target][1] - ONE;
11845             if (target == currentMove - 1) {
11846                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11847             }
11848             if (appData.highlightLastMove) {
11849                 SetHighlights(fromX, fromY, toX, toY);
11850             }
11851         }
11852     }
11853     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11854         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11855         while (currentMove > target) {
11856             SendToProgram("undo\n", &first);
11857             currentMove--;
11858         }
11859     } else {
11860         currentMove = target;
11861     }
11862     
11863     if (gameMode == EditGame || gameMode == EndOfGame) {
11864         whiteTimeRemaining = timeRemaining[0][currentMove];
11865         blackTimeRemaining = timeRemaining[1][currentMove];
11866     }
11867     DisplayBothClocks();
11868     DisplayMove(currentMove - 1);
11869     DrawPosition(full_redraw, boards[currentMove]);
11870     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11871     // [HGM] PV info: routine tests if comment empty
11872     DisplayComment(currentMove - 1, commentList[currentMove]);
11873 }
11874
11875 void
11876 BackwardEvent()
11877 {
11878     if (gameMode == IcsExamining && !pausing) {
11879         SendToICS(ics_prefix);
11880         SendToICS("backward\n");
11881     } else {
11882         BackwardInner(currentMove - 1);
11883     }
11884 }
11885
11886 void
11887 ToStartEvent()
11888 {
11889     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11890         /* to optimize, we temporarily turn off analysis mode while we undo
11891          * all the moves. Otherwise we get analysis output after each undo.
11892          */ 
11893         if (first.analysisSupport) {
11894           SendToProgram("exit\nforce\n", &first);
11895           first.analyzing = FALSE;
11896         }
11897     }
11898
11899     if (gameMode == IcsExamining && !pausing) {
11900         SendToICS(ics_prefix);
11901         SendToICS("backward 999999\n");
11902     } else {
11903         BackwardInner(backwardMostMove);
11904     }
11905
11906     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11907         /* we have fed all the moves, so reactivate analysis mode */
11908         SendToProgram("analyze\n", &first);
11909         first.analyzing = TRUE;
11910         /*first.maybeThinking = TRUE;*/
11911         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11912     }
11913 }
11914
11915 void
11916 ToNrEvent(int to)
11917 {
11918   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11919   if (to >= forwardMostMove) to = forwardMostMove;
11920   if (to <= backwardMostMove) to = backwardMostMove;
11921   if (to < currentMove) {
11922     BackwardInner(to);
11923   } else {
11924     ForwardInner(to);
11925   }
11926 }
11927
11928 void
11929 RevertEvent()
11930 {
11931     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11932         return;
11933     }
11934     if (gameMode != IcsExamining) {
11935         DisplayError(_("You are not examining a game"), 0);
11936         return;
11937     }
11938     if (pausing) {
11939         DisplayError(_("You can't revert while pausing"), 0);
11940         return;
11941     }
11942     SendToICS(ics_prefix);
11943     SendToICS("revert\n");
11944 }
11945
11946 void
11947 RetractMoveEvent()
11948 {
11949     switch (gameMode) {
11950       case MachinePlaysWhite:
11951       case MachinePlaysBlack:
11952         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11953             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11954             return;
11955         }
11956         if (forwardMostMove < 2) return;
11957         currentMove = forwardMostMove = forwardMostMove - 2;
11958         whiteTimeRemaining = timeRemaining[0][currentMove];
11959         blackTimeRemaining = timeRemaining[1][currentMove];
11960         DisplayBothClocks();
11961         DisplayMove(currentMove - 1);
11962         ClearHighlights();/*!! could figure this out*/
11963         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11964         SendToProgram("remove\n", &first);
11965         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11966         break;
11967
11968       case BeginningOfGame:
11969       default:
11970         break;
11971
11972       case IcsPlayingWhite:
11973       case IcsPlayingBlack:
11974         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11975             SendToICS(ics_prefix);
11976             SendToICS("takeback 2\n");
11977         } else {
11978             SendToICS(ics_prefix);
11979             SendToICS("takeback 1\n");
11980         }
11981         break;
11982     }
11983 }
11984
11985 void
11986 MoveNowEvent()
11987 {
11988     ChessProgramState *cps;
11989
11990     switch (gameMode) {
11991       case MachinePlaysWhite:
11992         if (!WhiteOnMove(forwardMostMove)) {
11993             DisplayError(_("It is your turn"), 0);
11994             return;
11995         }
11996         cps = &first;
11997         break;
11998       case MachinePlaysBlack:
11999         if (WhiteOnMove(forwardMostMove)) {
12000             DisplayError(_("It is your turn"), 0);
12001             return;
12002         }
12003         cps = &first;
12004         break;
12005       case TwoMachinesPlay:
12006         if (WhiteOnMove(forwardMostMove) ==
12007             (first.twoMachinesColor[0] == 'w')) {
12008             cps = &first;
12009         } else {
12010             cps = &second;
12011         }
12012         break;
12013       case BeginningOfGame:
12014       default:
12015         return;
12016     }
12017     SendToProgram("?\n", cps);
12018 }
12019
12020 void
12021 TruncateGameEvent()
12022 {
12023     EditGameEvent();
12024     if (gameMode != EditGame) return;
12025     TruncateGame();
12026 }
12027
12028 void
12029 TruncateGame()
12030 {
12031     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12032     if (forwardMostMove > currentMove) {
12033         if (gameInfo.resultDetails != NULL) {
12034             free(gameInfo.resultDetails);
12035             gameInfo.resultDetails = NULL;
12036             gameInfo.result = GameUnfinished;
12037         }
12038         forwardMostMove = currentMove;
12039         HistorySet(parseList, backwardMostMove, forwardMostMove,
12040                    currentMove-1);
12041     }
12042 }
12043
12044 void
12045 HintEvent()
12046 {
12047     if (appData.noChessProgram) return;
12048     switch (gameMode) {
12049       case MachinePlaysWhite:
12050         if (WhiteOnMove(forwardMostMove)) {
12051             DisplayError(_("Wait until your turn"), 0);
12052             return;
12053         }
12054         break;
12055       case BeginningOfGame:
12056       case MachinePlaysBlack:
12057         if (!WhiteOnMove(forwardMostMove)) {
12058             DisplayError(_("Wait until your turn"), 0);
12059             return;
12060         }
12061         break;
12062       default:
12063         DisplayError(_("No hint available"), 0);
12064         return;
12065     }
12066     SendToProgram("hint\n", &first);
12067     hintRequested = TRUE;
12068 }
12069
12070 void
12071 BookEvent()
12072 {
12073     if (appData.noChessProgram) return;
12074     switch (gameMode) {
12075       case MachinePlaysWhite:
12076         if (WhiteOnMove(forwardMostMove)) {
12077             DisplayError(_("Wait until your turn"), 0);
12078             return;
12079         }
12080         break;
12081       case BeginningOfGame:
12082       case MachinePlaysBlack:
12083         if (!WhiteOnMove(forwardMostMove)) {
12084             DisplayError(_("Wait until your turn"), 0);
12085             return;
12086         }
12087         break;
12088       case EditPosition:
12089         EditPositionDone(TRUE);
12090         break;
12091       case TwoMachinesPlay:
12092         return;
12093       default:
12094         break;
12095     }
12096     SendToProgram("bk\n", &first);
12097     bookOutput[0] = NULLCHAR;
12098     bookRequested = TRUE;
12099 }
12100
12101 void
12102 AboutGameEvent()
12103 {
12104     char *tags = PGNTags(&gameInfo);
12105     TagsPopUp(tags, CmailMsg());
12106     free(tags);
12107 }
12108
12109 /* end button procedures */
12110
12111 void
12112 PrintPosition(fp, move)
12113      FILE *fp;
12114      int move;
12115 {
12116     int i, j;
12117     
12118     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12119         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12120             char c = PieceToChar(boards[move][i][j]);
12121             fputc(c == 'x' ? '.' : c, fp);
12122             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12123         }
12124     }
12125     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12126       fprintf(fp, "white to play\n");
12127     else
12128       fprintf(fp, "black to play\n");
12129 }
12130
12131 void
12132 PrintOpponents(fp)
12133      FILE *fp;
12134 {
12135     if (gameInfo.white != NULL) {
12136         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12137     } else {
12138         fprintf(fp, "\n");
12139     }
12140 }
12141
12142 /* Find last component of program's own name, using some heuristics */
12143 void
12144 TidyProgramName(prog, host, buf)
12145      char *prog, *host, buf[MSG_SIZ];
12146 {
12147     char *p, *q;
12148     int local = (strcmp(host, "localhost") == 0);
12149     while (!local && (p = strchr(prog, ';')) != NULL) {
12150         p++;
12151         while (*p == ' ') p++;
12152         prog = p;
12153     }
12154     if (*prog == '"' || *prog == '\'') {
12155         q = strchr(prog + 1, *prog);
12156     } else {
12157         q = strchr(prog, ' ');
12158     }
12159     if (q == NULL) q = prog + strlen(prog);
12160     p = q;
12161     while (p >= prog && *p != '/' && *p != '\\') p--;
12162     p++;
12163     if(p == prog && *p == '"') p++;
12164     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12165     memcpy(buf, p, q - p);
12166     buf[q - p] = NULLCHAR;
12167     if (!local) {
12168         strcat(buf, "@");
12169         strcat(buf, host);
12170     }
12171 }
12172
12173 char *
12174 TimeControlTagValue()
12175 {
12176     char buf[MSG_SIZ];
12177     if (!appData.clockMode) {
12178         strcpy(buf, "-");
12179     } else if (movesPerSession > 0) {
12180         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12181     } else if (timeIncrement == 0) {
12182         sprintf(buf, "%ld", timeControl/1000);
12183     } else {
12184         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12185     }
12186     return StrSave(buf);
12187 }
12188
12189 void
12190 SetGameInfo()
12191 {
12192     /* This routine is used only for certain modes */
12193     VariantClass v = gameInfo.variant;
12194     ChessMove r = GameUnfinished;
12195     char *p = NULL;
12196
12197     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12198         r = gameInfo.result; 
12199         p = gameInfo.resultDetails; 
12200         gameInfo.resultDetails = NULL;
12201     }
12202     ClearGameInfo(&gameInfo);
12203     gameInfo.variant = v;
12204
12205     switch (gameMode) {
12206       case MachinePlaysWhite:
12207         gameInfo.event = StrSave( appData.pgnEventHeader );
12208         gameInfo.site = StrSave(HostName());
12209         gameInfo.date = PGNDate();
12210         gameInfo.round = StrSave("-");
12211         gameInfo.white = StrSave(first.tidy);
12212         gameInfo.black = StrSave(UserName());
12213         gameInfo.timeControl = TimeControlTagValue();
12214         break;
12215
12216       case MachinePlaysBlack:
12217         gameInfo.event = StrSave( appData.pgnEventHeader );
12218         gameInfo.site = StrSave(HostName());
12219         gameInfo.date = PGNDate();
12220         gameInfo.round = StrSave("-");
12221         gameInfo.white = StrSave(UserName());
12222         gameInfo.black = StrSave(first.tidy);
12223         gameInfo.timeControl = TimeControlTagValue();
12224         break;
12225
12226       case TwoMachinesPlay:
12227         gameInfo.event = StrSave( appData.pgnEventHeader );
12228         gameInfo.site = StrSave(HostName());
12229         gameInfo.date = PGNDate();
12230         if (matchGame > 0) {
12231             char buf[MSG_SIZ];
12232             sprintf(buf, "%d", matchGame);
12233             gameInfo.round = StrSave(buf);
12234         } else {
12235             gameInfo.round = StrSave("-");
12236         }
12237         if (first.twoMachinesColor[0] == 'w') {
12238             gameInfo.white = StrSave(first.tidy);
12239             gameInfo.black = StrSave(second.tidy);
12240         } else {
12241             gameInfo.white = StrSave(second.tidy);
12242             gameInfo.black = StrSave(first.tidy);
12243         }
12244         gameInfo.timeControl = TimeControlTagValue();
12245         break;
12246
12247       case EditGame:
12248         gameInfo.event = StrSave("Edited game");
12249         gameInfo.site = StrSave(HostName());
12250         gameInfo.date = PGNDate();
12251         gameInfo.round = StrSave("-");
12252         gameInfo.white = StrSave("-");
12253         gameInfo.black = StrSave("-");
12254         gameInfo.result = r;
12255         gameInfo.resultDetails = p;
12256         break;
12257
12258       case EditPosition:
12259         gameInfo.event = StrSave("Edited position");
12260         gameInfo.site = StrSave(HostName());
12261         gameInfo.date = PGNDate();
12262         gameInfo.round = StrSave("-");
12263         gameInfo.white = StrSave("-");
12264         gameInfo.black = StrSave("-");
12265         break;
12266
12267       case IcsPlayingWhite:
12268       case IcsPlayingBlack:
12269       case IcsObserving:
12270       case IcsExamining:
12271         break;
12272
12273       case PlayFromGameFile:
12274         gameInfo.event = StrSave("Game from non-PGN file");
12275         gameInfo.site = StrSave(HostName());
12276         gameInfo.date = PGNDate();
12277         gameInfo.round = StrSave("-");
12278         gameInfo.white = StrSave("?");
12279         gameInfo.black = StrSave("?");
12280         break;
12281
12282       default:
12283         break;
12284     }
12285 }
12286
12287 void
12288 ReplaceComment(index, text)
12289      int index;
12290      char *text;
12291 {
12292     int len;
12293
12294     while (*text == '\n') text++;
12295     len = strlen(text);
12296     while (len > 0 && text[len - 1] == '\n') len--;
12297
12298     if (commentList[index] != NULL)
12299       free(commentList[index]);
12300
12301     if (len == 0) {
12302         commentList[index] = NULL;
12303         return;
12304     }
12305   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12306       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12307       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12308     commentList[index] = (char *) malloc(len + 2);
12309     strncpy(commentList[index], text, len);
12310     commentList[index][len] = '\n';
12311     commentList[index][len + 1] = NULLCHAR;
12312   } else { 
12313     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12314     char *p;
12315     commentList[index] = (char *) malloc(len + 6);
12316     strcpy(commentList[index], "{\n");
12317     strncpy(commentList[index]+2, text, len);
12318     commentList[index][len+2] = NULLCHAR;
12319     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12320     strcat(commentList[index], "\n}\n");
12321   }
12322 }
12323
12324 void
12325 CrushCRs(text)
12326      char *text;
12327 {
12328   char *p = text;
12329   char *q = text;
12330   char ch;
12331
12332   do {
12333     ch = *p++;
12334     if (ch == '\r') continue;
12335     *q++ = ch;
12336   } while (ch != '\0');
12337 }
12338
12339 void
12340 AppendComment(index, text, addBraces)
12341      int index;
12342      char *text;
12343      Boolean addBraces; // [HGM] braces: tells if we should add {}
12344 {
12345     int oldlen, len;
12346     char *old;
12347
12348 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12349     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12350
12351     CrushCRs(text);
12352     while (*text == '\n') text++;
12353     len = strlen(text);
12354     while (len > 0 && text[len - 1] == '\n') len--;
12355
12356     if (len == 0) return;
12357
12358     if (commentList[index] != NULL) {
12359         old = commentList[index];
12360         oldlen = strlen(old);
12361         while(commentList[index][oldlen-1] ==  '\n')
12362           commentList[index][--oldlen] = NULLCHAR;
12363         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12364         strcpy(commentList[index], old);
12365         free(old);
12366         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12367         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12368           if(addBraces) addBraces = FALSE; else { text++; len--; }
12369           while (*text == '\n') { text++; len--; }
12370           commentList[index][--oldlen] = NULLCHAR;
12371       }
12372         if(addBraces) strcat(commentList[index], "\n{\n");
12373         else          strcat(commentList[index], "\n");
12374         strcat(commentList[index], text);
12375         if(addBraces) strcat(commentList[index], "\n}\n");
12376         else          strcat(commentList[index], "\n");
12377     } else {
12378         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12379         if(addBraces)
12380              strcpy(commentList[index], "{\n");
12381         else commentList[index][0] = NULLCHAR;
12382         strcat(commentList[index], text);
12383         strcat(commentList[index], "\n");
12384         if(addBraces) strcat(commentList[index], "}\n");
12385     }
12386 }
12387
12388 static char * FindStr( char * text, char * sub_text )
12389 {
12390     char * result = strstr( text, sub_text );
12391
12392     if( result != NULL ) {
12393         result += strlen( sub_text );
12394     }
12395
12396     return result;
12397 }
12398
12399 /* [AS] Try to extract PV info from PGN comment */
12400 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12401 char *GetInfoFromComment( int index, char * text )
12402 {
12403     char * sep = text;
12404
12405     if( text != NULL && index > 0 ) {
12406         int score = 0;
12407         int depth = 0;
12408         int time = -1, sec = 0, deci;
12409         char * s_eval = FindStr( text, "[%eval " );
12410         char * s_emt = FindStr( text, "[%emt " );
12411
12412         if( s_eval != NULL || s_emt != NULL ) {
12413             /* New style */
12414             char delim;
12415
12416             if( s_eval != NULL ) {
12417                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12418                     return text;
12419                 }
12420
12421                 if( delim != ']' ) {
12422                     return text;
12423                 }
12424             }
12425
12426             if( s_emt != NULL ) {
12427             }
12428                 return text;
12429         }
12430         else {
12431             /* We expect something like: [+|-]nnn.nn/dd */
12432             int score_lo = 0;
12433
12434             if(*text != '{') return text; // [HGM] braces: must be normal comment
12435
12436             sep = strchr( text, '/' );
12437             if( sep == NULL || sep < (text+4) ) {
12438                 return text;
12439             }
12440
12441             time = -1; sec = -1; deci = -1;
12442             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12443                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12444                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12445                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12446                 return text;
12447             }
12448
12449             if( score_lo < 0 || score_lo >= 100 ) {
12450                 return text;
12451             }
12452
12453             if(sec >= 0) time = 600*time + 10*sec; else
12454             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12455
12456             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12457
12458             /* [HGM] PV time: now locate end of PV info */
12459             while( *++sep >= '0' && *sep <= '9'); // strip depth
12460             if(time >= 0)
12461             while( *++sep >= '0' && *sep <= '9'); // strip time
12462             if(sec >= 0)
12463             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12464             if(deci >= 0)
12465             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12466             while(*sep == ' ') sep++;
12467         }
12468
12469         if( depth <= 0 ) {
12470             return text;
12471         }
12472
12473         if( time < 0 ) {
12474             time = -1;
12475         }
12476
12477         pvInfoList[index-1].depth = depth;
12478         pvInfoList[index-1].score = score;
12479         pvInfoList[index-1].time  = 10*time; // centi-sec
12480         if(*sep == '}') *sep = 0; else *--sep = '{';
12481     }
12482     return sep;
12483 }
12484
12485 void
12486 SendToProgram(message, cps)
12487      char *message;
12488      ChessProgramState *cps;
12489 {
12490     int count, outCount, error;
12491     char buf[MSG_SIZ];
12492
12493     if (cps->pr == NULL) return;
12494     Attention(cps);
12495     
12496     if (appData.debugMode) {
12497         TimeMark now;
12498         GetTimeMark(&now);
12499         fprintf(debugFP, "%ld >%-6s: %s", 
12500                 SubtractTimeMarks(&now, &programStartTime),
12501                 cps->which, message);
12502     }
12503     
12504     count = strlen(message);
12505     outCount = OutputToProcess(cps->pr, message, count, &error);
12506     if (outCount < count && !exiting 
12507                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12508         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12509         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12510             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12511                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12512                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12513             } else {
12514                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12515             }
12516             gameInfo.resultDetails = StrSave(buf);
12517         }
12518         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12519     }
12520 }
12521
12522 void
12523 ReceiveFromProgram(isr, closure, message, count, error)
12524      InputSourceRef isr;
12525      VOIDSTAR closure;
12526      char *message;
12527      int count;
12528      int error;
12529 {
12530     char *end_str;
12531     char buf[MSG_SIZ];
12532     ChessProgramState *cps = (ChessProgramState *)closure;
12533
12534     if (isr != cps->isr) return; /* Killed intentionally */
12535     if (count <= 0) {
12536         if (count == 0) {
12537             sprintf(buf,
12538                     _("Error: %s chess program (%s) exited unexpectedly"),
12539                     cps->which, cps->program);
12540         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12541                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12542                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12543                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12544                 } else {
12545                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12546                 }
12547                 gameInfo.resultDetails = StrSave(buf);
12548             }
12549             RemoveInputSource(cps->isr);
12550             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12551         } else {
12552             sprintf(buf,
12553                     _("Error reading from %s chess program (%s)"),
12554                     cps->which, cps->program);
12555             RemoveInputSource(cps->isr);
12556
12557             /* [AS] Program is misbehaving badly... kill it */
12558             if( count == -2 ) {
12559                 DestroyChildProcess( cps->pr, 9 );
12560                 cps->pr = NoProc;
12561             }
12562
12563             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12564         }
12565         return;
12566     }
12567     
12568     if ((end_str = strchr(message, '\r')) != NULL)
12569       *end_str = NULLCHAR;
12570     if ((end_str = strchr(message, '\n')) != NULL)
12571       *end_str = NULLCHAR;
12572     
12573     if (appData.debugMode) {
12574         TimeMark now; int print = 1;
12575         char *quote = ""; char c; int i;
12576
12577         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12578                 char start = message[0];
12579                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12580                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12581                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12582                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12583                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12584                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12585                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12586                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12587                         { quote = "# "; print = (appData.engineComments == 2); }
12588                 message[0] = start; // restore original message
12589         }
12590         if(print) {
12591                 GetTimeMark(&now);
12592                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12593                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12594                         quote,
12595                         message);
12596         }
12597     }
12598
12599     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12600     if (appData.icsEngineAnalyze) {
12601         if (strstr(message, "whisper") != NULL ||
12602              strstr(message, "kibitz") != NULL || 
12603             strstr(message, "tellics") != NULL) return;
12604     }
12605
12606     HandleMachineMove(message, cps);
12607 }
12608
12609
12610 void
12611 SendTimeControl(cps, mps, tc, inc, sd, st)
12612      ChessProgramState *cps;
12613      int mps, inc, sd, st;
12614      long tc;
12615 {
12616     char buf[MSG_SIZ];
12617     int seconds;
12618
12619     if( timeControl_2 > 0 ) {
12620         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12621             tc = timeControl_2;
12622         }
12623     }
12624     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12625     inc /= cps->timeOdds;
12626     st  /= cps->timeOdds;
12627
12628     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12629
12630     if (st > 0) {
12631       /* Set exact time per move, normally using st command */
12632       if (cps->stKludge) {
12633         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12634         seconds = st % 60;
12635         if (seconds == 0) {
12636           sprintf(buf, "level 1 %d\n", st/60);
12637         } else {
12638           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12639         }
12640       } else {
12641         sprintf(buf, "st %d\n", st);
12642       }
12643     } else {
12644       /* Set conventional or incremental time control, using level command */
12645       if (seconds == 0) {
12646         /* Note old gnuchess bug -- minutes:seconds used to not work.
12647            Fixed in later versions, but still avoid :seconds
12648            when seconds is 0. */
12649         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12650       } else {
12651         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12652                 seconds, inc/1000);
12653       }
12654     }
12655     SendToProgram(buf, cps);
12656
12657     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12658     /* Orthogonally, limit search to given depth */
12659     if (sd > 0) {
12660       if (cps->sdKludge) {
12661         sprintf(buf, "depth\n%d\n", sd);
12662       } else {
12663         sprintf(buf, "sd %d\n", sd);
12664       }
12665       SendToProgram(buf, cps);
12666     }
12667
12668     if(cps->nps > 0) { /* [HGM] nps */
12669         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12670         else {
12671                 sprintf(buf, "nps %d\n", cps->nps);
12672               SendToProgram(buf, cps);
12673         }
12674     }
12675 }
12676
12677 ChessProgramState *WhitePlayer()
12678 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12679 {
12680     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12681        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12682         return &second;
12683     return &first;
12684 }
12685
12686 void
12687 SendTimeRemaining(cps, machineWhite)
12688      ChessProgramState *cps;
12689      int /*boolean*/ machineWhite;
12690 {
12691     char message[MSG_SIZ];
12692     long time, otime;
12693
12694     /* Note: this routine must be called when the clocks are stopped
12695        or when they have *just* been set or switched; otherwise
12696        it will be off by the time since the current tick started.
12697     */
12698     if (machineWhite) {
12699         time = whiteTimeRemaining / 10;
12700         otime = blackTimeRemaining / 10;
12701     } else {
12702         time = blackTimeRemaining / 10;
12703         otime = whiteTimeRemaining / 10;
12704     }
12705     /* [HGM] translate opponent's time by time-odds factor */
12706     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12707     if (appData.debugMode) {
12708         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12709     }
12710
12711     if (time <= 0) time = 1;
12712     if (otime <= 0) otime = 1;
12713     
12714     sprintf(message, "time %ld\n", time);
12715     SendToProgram(message, cps);
12716
12717     sprintf(message, "otim %ld\n", otime);
12718     SendToProgram(message, cps);
12719 }
12720
12721 int
12722 BoolFeature(p, name, loc, cps)
12723      char **p;
12724      char *name;
12725      int *loc;
12726      ChessProgramState *cps;
12727 {
12728   char buf[MSG_SIZ];
12729   int len = strlen(name);
12730   int val;
12731   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12732     (*p) += len + 1;
12733     sscanf(*p, "%d", &val);
12734     *loc = (val != 0);
12735     while (**p && **p != ' ') (*p)++;
12736     sprintf(buf, "accepted %s\n", name);
12737     SendToProgram(buf, cps);
12738     return TRUE;
12739   }
12740   return FALSE;
12741 }
12742
12743 int
12744 IntFeature(p, name, loc, cps)
12745      char **p;
12746      char *name;
12747      int *loc;
12748      ChessProgramState *cps;
12749 {
12750   char buf[MSG_SIZ];
12751   int len = strlen(name);
12752   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12753     (*p) += len + 1;
12754     sscanf(*p, "%d", loc);
12755     while (**p && **p != ' ') (*p)++;
12756     sprintf(buf, "accepted %s\n", name);
12757     SendToProgram(buf, cps);
12758     return TRUE;
12759   }
12760   return FALSE;
12761 }
12762
12763 int
12764 StringFeature(p, name, loc, cps)
12765      char **p;
12766      char *name;
12767      char loc[];
12768      ChessProgramState *cps;
12769 {
12770   char buf[MSG_SIZ];
12771   int len = strlen(name);
12772   if (strncmp((*p), name, len) == 0
12773       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12774     (*p) += len + 2;
12775     sscanf(*p, "%[^\"]", loc);
12776     while (**p && **p != '\"') (*p)++;
12777     if (**p == '\"') (*p)++;
12778     sprintf(buf, "accepted %s\n", name);
12779     SendToProgram(buf, cps);
12780     return TRUE;
12781   }
12782   return FALSE;
12783 }
12784
12785 int 
12786 ParseOption(Option *opt, ChessProgramState *cps)
12787 // [HGM] options: process the string that defines an engine option, and determine
12788 // name, type, default value, and allowed value range
12789 {
12790         char *p, *q, buf[MSG_SIZ];
12791         int n, min = (-1)<<31, max = 1<<31, def;
12792
12793         if(p = strstr(opt->name, " -spin ")) {
12794             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12795             if(max < min) max = min; // enforce consistency
12796             if(def < min) def = min;
12797             if(def > max) def = max;
12798             opt->value = def;
12799             opt->min = min;
12800             opt->max = max;
12801             opt->type = Spin;
12802         } else if((p = strstr(opt->name, " -slider "))) {
12803             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12804             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12805             if(max < min) max = min; // enforce consistency
12806             if(def < min) def = min;
12807             if(def > max) def = max;
12808             opt->value = def;
12809             opt->min = min;
12810             opt->max = max;
12811             opt->type = Spin; // Slider;
12812         } else if((p = strstr(opt->name, " -string "))) {
12813             opt->textValue = p+9;
12814             opt->type = TextBox;
12815         } else if((p = strstr(opt->name, " -file "))) {
12816             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12817             opt->textValue = p+7;
12818             opt->type = TextBox; // FileName;
12819         } else if((p = strstr(opt->name, " -path "))) {
12820             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12821             opt->textValue = p+7;
12822             opt->type = TextBox; // PathName;
12823         } else if(p = strstr(opt->name, " -check ")) {
12824             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12825             opt->value = (def != 0);
12826             opt->type = CheckBox;
12827         } else if(p = strstr(opt->name, " -combo ")) {
12828             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12829             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12830             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12831             opt->value = n = 0;
12832             while(q = StrStr(q, " /// ")) {
12833                 n++; *q = 0;    // count choices, and null-terminate each of them
12834                 q += 5;
12835                 if(*q == '*') { // remember default, which is marked with * prefix
12836                     q++;
12837                     opt->value = n;
12838                 }
12839                 cps->comboList[cps->comboCnt++] = q;
12840             }
12841             cps->comboList[cps->comboCnt++] = NULL;
12842             opt->max = n + 1;
12843             opt->type = ComboBox;
12844         } else if(p = strstr(opt->name, " -button")) {
12845             opt->type = Button;
12846         } else if(p = strstr(opt->name, " -save")) {
12847             opt->type = SaveButton;
12848         } else return FALSE;
12849         *p = 0; // terminate option name
12850         // now look if the command-line options define a setting for this engine option.
12851         if(cps->optionSettings && cps->optionSettings[0])
12852             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12853         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12854                 sprintf(buf, "option %s", p);
12855                 if(p = strstr(buf, ",")) *p = 0;
12856                 strcat(buf, "\n");
12857                 SendToProgram(buf, cps);
12858         }
12859         return TRUE;
12860 }
12861
12862 void
12863 FeatureDone(cps, val)
12864      ChessProgramState* cps;
12865      int val;
12866 {
12867   DelayedEventCallback cb = GetDelayedEvent();
12868   if ((cb == InitBackEnd3 && cps == &first) ||
12869       (cb == TwoMachinesEventIfReady && cps == &second)) {
12870     CancelDelayedEvent();
12871     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12872   }
12873   cps->initDone = val;
12874 }
12875
12876 /* Parse feature command from engine */
12877 void
12878 ParseFeatures(args, cps)
12879      char* args;
12880      ChessProgramState *cps;  
12881 {
12882   char *p = args;
12883   char *q;
12884   int val;
12885   char buf[MSG_SIZ];
12886
12887   for (;;) {
12888     while (*p == ' ') p++;
12889     if (*p == NULLCHAR) return;
12890
12891     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12892     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12893     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12894     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12895     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12896     if (BoolFeature(&p, "reuse", &val, cps)) {
12897       /* Engine can disable reuse, but can't enable it if user said no */
12898       if (!val) cps->reuse = FALSE;
12899       continue;
12900     }
12901     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12902     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12903       if (gameMode == TwoMachinesPlay) {
12904         DisplayTwoMachinesTitle();
12905       } else {
12906         DisplayTitle("");
12907       }
12908       continue;
12909     }
12910     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12911     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12912     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12913     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12914     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12915     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12916     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12917     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12918     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12919     if (IntFeature(&p, "done", &val, cps)) {
12920       FeatureDone(cps, val);
12921       continue;
12922     }
12923     /* Added by Tord: */
12924     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12925     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12926     /* End of additions by Tord */
12927
12928     /* [HGM] added features: */
12929     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12930     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12931     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12932     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12933     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12934     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12935     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12936         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12937             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12938             SendToProgram(buf, cps);
12939             continue;
12940         }
12941         if(cps->nrOptions >= MAX_OPTIONS) {
12942             cps->nrOptions--;
12943             sprintf(buf, "%s engine has too many options\n", cps->which);
12944             DisplayError(buf, 0);
12945         }
12946         continue;
12947     }
12948     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12949     /* End of additions by HGM */
12950
12951     /* unknown feature: complain and skip */
12952     q = p;
12953     while (*q && *q != '=') q++;
12954     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12955     SendToProgram(buf, cps);
12956     p = q;
12957     if (*p == '=') {
12958       p++;
12959       if (*p == '\"') {
12960         p++;
12961         while (*p && *p != '\"') p++;
12962         if (*p == '\"') p++;
12963       } else {
12964         while (*p && *p != ' ') p++;
12965       }
12966     }
12967   }
12968
12969 }
12970
12971 void
12972 PeriodicUpdatesEvent(newState)
12973      int newState;
12974 {
12975     if (newState == appData.periodicUpdates)
12976       return;
12977
12978     appData.periodicUpdates=newState;
12979
12980     /* Display type changes, so update it now */
12981 //    DisplayAnalysis();
12982
12983     /* Get the ball rolling again... */
12984     if (newState) {
12985         AnalysisPeriodicEvent(1);
12986         StartAnalysisClock();
12987     }
12988 }
12989
12990 void
12991 PonderNextMoveEvent(newState)
12992      int newState;
12993 {
12994     if (newState == appData.ponderNextMove) return;
12995     if (gameMode == EditPosition) EditPositionDone(TRUE);
12996     if (newState) {
12997         SendToProgram("hard\n", &first);
12998         if (gameMode == TwoMachinesPlay) {
12999             SendToProgram("hard\n", &second);
13000         }
13001     } else {
13002         SendToProgram("easy\n", &first);
13003         thinkOutput[0] = NULLCHAR;
13004         if (gameMode == TwoMachinesPlay) {
13005             SendToProgram("easy\n", &second);
13006         }
13007     }
13008     appData.ponderNextMove = newState;
13009 }
13010
13011 void
13012 NewSettingEvent(option, command, value)
13013      char *command;
13014      int option, value;
13015 {
13016     char buf[MSG_SIZ];
13017
13018     if (gameMode == EditPosition) EditPositionDone(TRUE);
13019     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13020     SendToProgram(buf, &first);
13021     if (gameMode == TwoMachinesPlay) {
13022         SendToProgram(buf, &second);
13023     }
13024 }
13025
13026 void
13027 ShowThinkingEvent()
13028 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13029 {
13030     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13031     int newState = appData.showThinking
13032         // [HGM] thinking: other features now need thinking output as well
13033         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13034     
13035     if (oldState == newState) return;
13036     oldState = newState;
13037     if (gameMode == EditPosition) EditPositionDone(TRUE);
13038     if (oldState) {
13039         SendToProgram("post\n", &first);
13040         if (gameMode == TwoMachinesPlay) {
13041             SendToProgram("post\n", &second);
13042         }
13043     } else {
13044         SendToProgram("nopost\n", &first);
13045         thinkOutput[0] = NULLCHAR;
13046         if (gameMode == TwoMachinesPlay) {
13047             SendToProgram("nopost\n", &second);
13048         }
13049     }
13050 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13051 }
13052
13053 void
13054 AskQuestionEvent(title, question, replyPrefix, which)
13055      char *title; char *question; char *replyPrefix; char *which;
13056 {
13057   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13058   if (pr == NoProc) return;
13059   AskQuestion(title, question, replyPrefix, pr);
13060 }
13061
13062 void
13063 DisplayMove(moveNumber)
13064      int moveNumber;
13065 {
13066     char message[MSG_SIZ];
13067     char res[MSG_SIZ];
13068     char cpThinkOutput[MSG_SIZ];
13069
13070     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13071     
13072     if (moveNumber == forwardMostMove - 1 || 
13073         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13074
13075         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13076
13077         if (strchr(cpThinkOutput, '\n')) {
13078             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13079         }
13080     } else {
13081         *cpThinkOutput = NULLCHAR;
13082     }
13083
13084     /* [AS] Hide thinking from human user */
13085     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13086         *cpThinkOutput = NULLCHAR;
13087         if( thinkOutput[0] != NULLCHAR ) {
13088             int i;
13089
13090             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13091                 cpThinkOutput[i] = '.';
13092             }
13093             cpThinkOutput[i] = NULLCHAR;
13094             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13095         }
13096     }
13097
13098     if (moveNumber == forwardMostMove - 1 &&
13099         gameInfo.resultDetails != NULL) {
13100         if (gameInfo.resultDetails[0] == NULLCHAR) {
13101             sprintf(res, " %s", PGNResult(gameInfo.result));
13102         } else {
13103             sprintf(res, " {%s} %s",
13104                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13105         }
13106     } else {
13107         res[0] = NULLCHAR;
13108     }
13109
13110     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13111         DisplayMessage(res, cpThinkOutput);
13112     } else {
13113         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13114                 WhiteOnMove(moveNumber) ? " " : ".. ",
13115                 parseList[moveNumber], res);
13116         DisplayMessage(message, cpThinkOutput);
13117     }
13118 }
13119
13120 void
13121 DisplayComment(moveNumber, text)
13122      int moveNumber;
13123      char *text;
13124 {
13125     char title[MSG_SIZ];
13126     char buf[8000]; // comment can be long!
13127     int score, depth;
13128     
13129     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13130       strcpy(title, "Comment");
13131     } else {
13132       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13133               WhiteOnMove(moveNumber) ? " " : ".. ",
13134               parseList[moveNumber]);
13135     }
13136     // [HGM] PV info: display PV info together with (or as) comment
13137     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13138       if(text == NULL) text = "";                                           
13139       score = pvInfoList[moveNumber].score;
13140       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13141               depth, (pvInfoList[moveNumber].time+50)/100, text);
13142       text = buf;
13143     }
13144     if (text != NULL && (appData.autoDisplayComment || commentUp))
13145         CommentPopUp(title, text);
13146 }
13147
13148 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13149  * might be busy thinking or pondering.  It can be omitted if your
13150  * gnuchess is configured to stop thinking immediately on any user
13151  * input.  However, that gnuchess feature depends on the FIONREAD
13152  * ioctl, which does not work properly on some flavors of Unix.
13153  */
13154 void
13155 Attention(cps)
13156      ChessProgramState *cps;
13157 {
13158 #if ATTENTION
13159     if (!cps->useSigint) return;
13160     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13161     switch (gameMode) {
13162       case MachinePlaysWhite:
13163       case MachinePlaysBlack:
13164       case TwoMachinesPlay:
13165       case IcsPlayingWhite:
13166       case IcsPlayingBlack:
13167       case AnalyzeMode:
13168       case AnalyzeFile:
13169         /* Skip if we know it isn't thinking */
13170         if (!cps->maybeThinking) return;
13171         if (appData.debugMode)
13172           fprintf(debugFP, "Interrupting %s\n", cps->which);
13173         InterruptChildProcess(cps->pr);
13174         cps->maybeThinking = FALSE;
13175         break;
13176       default:
13177         break;
13178     }
13179 #endif /*ATTENTION*/
13180 }
13181
13182 int
13183 CheckFlags()
13184 {
13185     if (whiteTimeRemaining <= 0) {
13186         if (!whiteFlag) {
13187             whiteFlag = TRUE;
13188             if (appData.icsActive) {
13189                 if (appData.autoCallFlag &&
13190                     gameMode == IcsPlayingBlack && !blackFlag) {
13191                   SendToICS(ics_prefix);
13192                   SendToICS("flag\n");
13193                 }
13194             } else {
13195                 if (blackFlag) {
13196                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13197                 } else {
13198                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13199                     if (appData.autoCallFlag) {
13200                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13201                         return TRUE;
13202                     }
13203                 }
13204             }
13205         }
13206     }
13207     if (blackTimeRemaining <= 0) {
13208         if (!blackFlag) {
13209             blackFlag = TRUE;
13210             if (appData.icsActive) {
13211                 if (appData.autoCallFlag &&
13212                     gameMode == IcsPlayingWhite && !whiteFlag) {
13213                   SendToICS(ics_prefix);
13214                   SendToICS("flag\n");
13215                 }
13216             } else {
13217                 if (whiteFlag) {
13218                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13219                 } else {
13220                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13221                     if (appData.autoCallFlag) {
13222                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13223                         return TRUE;
13224                     }
13225                 }
13226             }
13227         }
13228     }
13229     return FALSE;
13230 }
13231
13232 void
13233 CheckTimeControl()
13234 {
13235     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13236         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13237
13238     /*
13239      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13240      */
13241     if ( !WhiteOnMove(forwardMostMove) )
13242         /* White made time control */
13243         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13244         /* [HGM] time odds: correct new time quota for time odds! */
13245                                             / WhitePlayer()->timeOdds;
13246       else
13247         /* Black made time control */
13248         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13249                                             / WhitePlayer()->other->timeOdds;
13250 }
13251
13252 void
13253 DisplayBothClocks()
13254 {
13255     int wom = gameMode == EditPosition ?
13256       !blackPlaysFirst : WhiteOnMove(currentMove);
13257     DisplayWhiteClock(whiteTimeRemaining, wom);
13258     DisplayBlackClock(blackTimeRemaining, !wom);
13259 }
13260
13261
13262 /* Timekeeping seems to be a portability nightmare.  I think everyone
13263    has ftime(), but I'm really not sure, so I'm including some ifdefs
13264    to use other calls if you don't.  Clocks will be less accurate if
13265    you have neither ftime nor gettimeofday.
13266 */
13267
13268 /* VS 2008 requires the #include outside of the function */
13269 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13270 #include <sys/timeb.h>
13271 #endif
13272
13273 /* Get the current time as a TimeMark */
13274 void
13275 GetTimeMark(tm)
13276      TimeMark *tm;
13277 {
13278 #if HAVE_GETTIMEOFDAY
13279
13280     struct timeval timeVal;
13281     struct timezone timeZone;
13282
13283     gettimeofday(&timeVal, &timeZone);
13284     tm->sec = (long) timeVal.tv_sec; 
13285     tm->ms = (int) (timeVal.tv_usec / 1000L);
13286
13287 #else /*!HAVE_GETTIMEOFDAY*/
13288 #if HAVE_FTIME
13289
13290 // include <sys/timeb.h> / moved to just above start of function
13291     struct timeb timeB;
13292
13293     ftime(&timeB);
13294     tm->sec = (long) timeB.time;
13295     tm->ms = (int) timeB.millitm;
13296
13297 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13298     tm->sec = (long) time(NULL);
13299     tm->ms = 0;
13300 #endif
13301 #endif
13302 }
13303
13304 /* Return the difference in milliseconds between two
13305    time marks.  We assume the difference will fit in a long!
13306 */
13307 long
13308 SubtractTimeMarks(tm2, tm1)
13309      TimeMark *tm2, *tm1;
13310 {
13311     return 1000L*(tm2->sec - tm1->sec) +
13312            (long) (tm2->ms - tm1->ms);
13313 }
13314
13315
13316 /*
13317  * Code to manage the game clocks.
13318  *
13319  * In tournament play, black starts the clock and then white makes a move.
13320  * We give the human user a slight advantage if he is playing white---the
13321  * clocks don't run until he makes his first move, so it takes zero time.
13322  * Also, we don't account for network lag, so we could get out of sync
13323  * with GNU Chess's clock -- but then, referees are always right.  
13324  */
13325
13326 static TimeMark tickStartTM;
13327 static long intendedTickLength;
13328
13329 long
13330 NextTickLength(timeRemaining)
13331      long timeRemaining;
13332 {
13333     long nominalTickLength, nextTickLength;
13334
13335     if (timeRemaining > 0L && timeRemaining <= 10000L)
13336       nominalTickLength = 100L;
13337     else
13338       nominalTickLength = 1000L;
13339     nextTickLength = timeRemaining % nominalTickLength;
13340     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13341
13342     return nextTickLength;
13343 }
13344
13345 /* Adjust clock one minute up or down */
13346 void
13347 AdjustClock(Boolean which, int dir)
13348 {
13349     if(which) blackTimeRemaining += 60000*dir;
13350     else      whiteTimeRemaining += 60000*dir;
13351     DisplayBothClocks();
13352 }
13353
13354 /* Stop clocks and reset to a fresh time control */
13355 void
13356 ResetClocks() 
13357 {
13358     (void) StopClockTimer();
13359     if (appData.icsActive) {
13360         whiteTimeRemaining = blackTimeRemaining = 0;
13361     } else if (searchTime) {
13362         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13363         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13364     } else { /* [HGM] correct new time quote for time odds */
13365         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13366         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13367     }
13368     if (whiteFlag || blackFlag) {
13369         DisplayTitle("");
13370         whiteFlag = blackFlag = FALSE;
13371     }
13372     DisplayBothClocks();
13373 }
13374
13375 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13376
13377 /* Decrement running clock by amount of time that has passed */
13378 void
13379 DecrementClocks()
13380 {
13381     long timeRemaining;
13382     long lastTickLength, fudge;
13383     TimeMark now;
13384
13385     if (!appData.clockMode) return;
13386     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13387         
13388     GetTimeMark(&now);
13389
13390     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13391
13392     /* Fudge if we woke up a little too soon */
13393     fudge = intendedTickLength - lastTickLength;
13394     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13395
13396     if (WhiteOnMove(forwardMostMove)) {
13397         if(whiteNPS >= 0) lastTickLength = 0;
13398         timeRemaining = whiteTimeRemaining -= lastTickLength;
13399         DisplayWhiteClock(whiteTimeRemaining - fudge,
13400                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13401     } else {
13402         if(blackNPS >= 0) lastTickLength = 0;
13403         timeRemaining = blackTimeRemaining -= lastTickLength;
13404         DisplayBlackClock(blackTimeRemaining - fudge,
13405                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13406     }
13407
13408     if (CheckFlags()) return;
13409         
13410     tickStartTM = now;
13411     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13412     StartClockTimer(intendedTickLength);
13413
13414     /* if the time remaining has fallen below the alarm threshold, sound the
13415      * alarm. if the alarm has sounded and (due to a takeback or time control
13416      * with increment) the time remaining has increased to a level above the
13417      * threshold, reset the alarm so it can sound again. 
13418      */
13419     
13420     if (appData.icsActive && appData.icsAlarm) {
13421
13422         /* make sure we are dealing with the user's clock */
13423         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13424                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13425            )) return;
13426
13427         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13428             alarmSounded = FALSE;
13429         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13430             PlayAlarmSound();
13431             alarmSounded = TRUE;
13432         }
13433     }
13434 }
13435
13436
13437 /* A player has just moved, so stop the previously running
13438    clock and (if in clock mode) start the other one.
13439    We redisplay both clocks in case we're in ICS mode, because
13440    ICS gives us an update to both clocks after every move.
13441    Note that this routine is called *after* forwardMostMove
13442    is updated, so the last fractional tick must be subtracted
13443    from the color that is *not* on move now.
13444 */
13445 void
13446 SwitchClocks()
13447 {
13448     long lastTickLength;
13449     TimeMark now;
13450     int flagged = FALSE;
13451
13452     GetTimeMark(&now);
13453
13454     if (StopClockTimer() && appData.clockMode) {
13455         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13456         if (WhiteOnMove(forwardMostMove)) {
13457             if(blackNPS >= 0) lastTickLength = 0;
13458             blackTimeRemaining -= lastTickLength;
13459            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13460 //         if(pvInfoList[forwardMostMove-1].time == -1)
13461                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13462                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13463         } else {
13464            if(whiteNPS >= 0) lastTickLength = 0;
13465            whiteTimeRemaining -= lastTickLength;
13466            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13467 //         if(pvInfoList[forwardMostMove-1].time == -1)
13468                  pvInfoList[forwardMostMove-1].time = 
13469                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13470         }
13471         flagged = CheckFlags();
13472     }
13473     CheckTimeControl();
13474
13475     if (flagged || !appData.clockMode) return;
13476
13477     switch (gameMode) {
13478       case MachinePlaysBlack:
13479       case MachinePlaysWhite:
13480       case BeginningOfGame:
13481         if (pausing) return;
13482         break;
13483
13484       case EditGame:
13485       case PlayFromGameFile:
13486       case IcsExamining:
13487         return;
13488
13489       default:
13490         break;
13491     }
13492
13493     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13494         if(WhiteOnMove(forwardMostMove))
13495              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13496         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13497     }
13498
13499     tickStartTM = now;
13500     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13501       whiteTimeRemaining : blackTimeRemaining);
13502     StartClockTimer(intendedTickLength);
13503 }
13504         
13505
13506 /* Stop both clocks */
13507 void
13508 StopClocks()
13509 {       
13510     long lastTickLength;
13511     TimeMark now;
13512
13513     if (!StopClockTimer()) return;
13514     if (!appData.clockMode) return;
13515
13516     GetTimeMark(&now);
13517
13518     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13519     if (WhiteOnMove(forwardMostMove)) {
13520         if(whiteNPS >= 0) lastTickLength = 0;
13521         whiteTimeRemaining -= lastTickLength;
13522         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13523     } else {
13524         if(blackNPS >= 0) lastTickLength = 0;
13525         blackTimeRemaining -= lastTickLength;
13526         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13527     }
13528     CheckFlags();
13529 }
13530         
13531 /* Start clock of player on move.  Time may have been reset, so
13532    if clock is already running, stop and restart it. */
13533 void
13534 StartClocks()
13535 {
13536     (void) StopClockTimer(); /* in case it was running already */
13537     DisplayBothClocks();
13538     if (CheckFlags()) return;
13539
13540     if (!appData.clockMode) return;
13541     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13542
13543     GetTimeMark(&tickStartTM);
13544     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13545       whiteTimeRemaining : blackTimeRemaining);
13546
13547    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13548     whiteNPS = blackNPS = -1; 
13549     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13550        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13551         whiteNPS = first.nps;
13552     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13553        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13554         blackNPS = first.nps;
13555     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13556         whiteNPS = second.nps;
13557     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13558         blackNPS = second.nps;
13559     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13560
13561     StartClockTimer(intendedTickLength);
13562 }
13563
13564 char *
13565 TimeString(ms)
13566      long ms;
13567 {
13568     long second, minute, hour, day;
13569     char *sign = "";
13570     static char buf[32];
13571     
13572     if (ms > 0 && ms <= 9900) {
13573       /* convert milliseconds to tenths, rounding up */
13574       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13575
13576       sprintf(buf, " %03.1f ", tenths/10.0);
13577       return buf;
13578     }
13579
13580     /* convert milliseconds to seconds, rounding up */
13581     /* use floating point to avoid strangeness of integer division
13582        with negative dividends on many machines */
13583     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13584
13585     if (second < 0) {
13586         sign = "-";
13587         second = -second;
13588     }
13589     
13590     day = second / (60 * 60 * 24);
13591     second = second % (60 * 60 * 24);
13592     hour = second / (60 * 60);
13593     second = second % (60 * 60);
13594     minute = second / 60;
13595     second = second % 60;
13596     
13597     if (day > 0)
13598       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13599               sign, day, hour, minute, second);
13600     else if (hour > 0)
13601       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13602     else
13603       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13604     
13605     return buf;
13606 }
13607
13608
13609 /*
13610  * This is necessary because some C libraries aren't ANSI C compliant yet.
13611  */
13612 char *
13613 StrStr(string, match)
13614      char *string, *match;
13615 {
13616     int i, length;
13617     
13618     length = strlen(match);
13619     
13620     for (i = strlen(string) - length; i >= 0; i--, string++)
13621       if (!strncmp(match, string, length))
13622         return string;
13623     
13624     return NULL;
13625 }
13626
13627 char *
13628 StrCaseStr(string, match)
13629      char *string, *match;
13630 {
13631     int i, j, length;
13632     
13633     length = strlen(match);
13634     
13635     for (i = strlen(string) - length; i >= 0; i--, string++) {
13636         for (j = 0; j < length; j++) {
13637             if (ToLower(match[j]) != ToLower(string[j]))
13638               break;
13639         }
13640         if (j == length) return string;
13641     }
13642
13643     return NULL;
13644 }
13645
13646 #ifndef _amigados
13647 int
13648 StrCaseCmp(s1, s2)
13649      char *s1, *s2;
13650 {
13651     char c1, c2;
13652     
13653     for (;;) {
13654         c1 = ToLower(*s1++);
13655         c2 = ToLower(*s2++);
13656         if (c1 > c2) return 1;
13657         if (c1 < c2) return -1;
13658         if (c1 == NULLCHAR) return 0;
13659     }
13660 }
13661
13662
13663 int
13664 ToLower(c)
13665      int c;
13666 {
13667     return isupper(c) ? tolower(c) : c;
13668 }
13669
13670
13671 int
13672 ToUpper(c)
13673      int c;
13674 {
13675     return islower(c) ? toupper(c) : c;
13676 }
13677 #endif /* !_amigados    */
13678
13679 char *
13680 StrSave(s)
13681      char *s;
13682 {
13683     char *ret;
13684
13685     if ((ret = (char *) malloc(strlen(s) + 1))) {
13686         strcpy(ret, s);
13687     }
13688     return ret;
13689 }
13690
13691 char *
13692 StrSavePtr(s, savePtr)
13693      char *s, **savePtr;
13694 {
13695     if (*savePtr) {
13696         free(*savePtr);
13697     }
13698     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13699         strcpy(*savePtr, s);
13700     }
13701     return(*savePtr);
13702 }
13703
13704 char *
13705 PGNDate()
13706 {
13707     time_t clock;
13708     struct tm *tm;
13709     char buf[MSG_SIZ];
13710
13711     clock = time((time_t *)NULL);
13712     tm = localtime(&clock);
13713     sprintf(buf, "%04d.%02d.%02d",
13714             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13715     return StrSave(buf);
13716 }
13717
13718
13719 char *
13720 PositionToFEN(move, overrideCastling)
13721      int move;
13722      char *overrideCastling;
13723 {
13724     int i, j, fromX, fromY, toX, toY;
13725     int whiteToPlay;
13726     char buf[128];
13727     char *p, *q;
13728     int emptycount;
13729     ChessSquare piece;
13730
13731     whiteToPlay = (gameMode == EditPosition) ?
13732       !blackPlaysFirst : (move % 2 == 0);
13733     p = buf;
13734
13735     /* Piece placement data */
13736     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13737         emptycount = 0;
13738         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13739             if (boards[move][i][j] == EmptySquare) {
13740                 emptycount++;
13741             } else { ChessSquare piece = boards[move][i][j];
13742                 if (emptycount > 0) {
13743                     if(emptycount<10) /* [HGM] can be >= 10 */
13744                         *p++ = '0' + emptycount;
13745                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13746                     emptycount = 0;
13747                 }
13748                 if(PieceToChar(piece) == '+') {
13749                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13750                     *p++ = '+';
13751                     piece = (ChessSquare)(DEMOTED piece);
13752                 } 
13753                 *p++ = PieceToChar(piece);
13754                 if(p[-1] == '~') {
13755                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13756                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13757                     *p++ = '~';
13758                 }
13759             }
13760         }
13761         if (emptycount > 0) {
13762             if(emptycount<10) /* [HGM] can be >= 10 */
13763                 *p++ = '0' + emptycount;
13764             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13765             emptycount = 0;
13766         }
13767         *p++ = '/';
13768     }
13769     *(p - 1) = ' ';
13770
13771     /* [HGM] print Crazyhouse or Shogi holdings */
13772     if( gameInfo.holdingsWidth ) {
13773         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13774         q = p;
13775         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13776             piece = boards[move][i][BOARD_WIDTH-1];
13777             if( piece != EmptySquare )
13778               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13779                   *p++ = PieceToChar(piece);
13780         }
13781         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13782             piece = boards[move][BOARD_HEIGHT-i-1][0];
13783             if( piece != EmptySquare )
13784               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13785                   *p++ = PieceToChar(piece);
13786         }
13787
13788         if( q == p ) *p++ = '-';
13789         *p++ = ']';
13790         *p++ = ' ';
13791     }
13792
13793     /* Active color */
13794     *p++ = whiteToPlay ? 'w' : 'b';
13795     *p++ = ' ';
13796
13797   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13798     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13799   } else {
13800   if(nrCastlingRights) {
13801      q = p;
13802      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13803        /* [HGM] write directly from rights */
13804            if(boards[move][CASTLING][2] != NoRights &&
13805               boards[move][CASTLING][0] != NoRights   )
13806                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13807            if(boards[move][CASTLING][2] != NoRights &&
13808               boards[move][CASTLING][1] != NoRights   )
13809                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13810            if(boards[move][CASTLING][5] != NoRights &&
13811               boards[move][CASTLING][3] != NoRights   )
13812                 *p++ = boards[move][CASTLING][3] + AAA;
13813            if(boards[move][CASTLING][5] != NoRights &&
13814               boards[move][CASTLING][4] != NoRights   )
13815                 *p++ = boards[move][CASTLING][4] + AAA;
13816      } else {
13817
13818         /* [HGM] write true castling rights */
13819         if( nrCastlingRights == 6 ) {
13820             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13821                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13822             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13823                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13824             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13825                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13826             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13827                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13828         }
13829      }
13830      if (q == p) *p++ = '-'; /* No castling rights */
13831      *p++ = ' ';
13832   }
13833
13834   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13835      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13836     /* En passant target square */
13837     if (move > backwardMostMove) {
13838         fromX = moveList[move - 1][0] - AAA;
13839         fromY = moveList[move - 1][1] - ONE;
13840         toX = moveList[move - 1][2] - AAA;
13841         toY = moveList[move - 1][3] - ONE;
13842         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13843             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13844             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13845             fromX == toX) {
13846             /* 2-square pawn move just happened */
13847             *p++ = toX + AAA;
13848             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13849         } else {
13850             *p++ = '-';
13851         }
13852     } else if(move == backwardMostMove) {
13853         // [HGM] perhaps we should always do it like this, and forget the above?
13854         if((signed char)boards[move][EP_STATUS] >= 0) {
13855             *p++ = boards[move][EP_STATUS] + AAA;
13856             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13857         } else {
13858             *p++ = '-';
13859         }
13860     } else {
13861         *p++ = '-';
13862     }
13863     *p++ = ' ';
13864   }
13865   }
13866
13867     /* [HGM] find reversible plies */
13868     {   int i = 0, j=move;
13869
13870         if (appData.debugMode) { int k;
13871             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13872             for(k=backwardMostMove; k<=forwardMostMove; k++)
13873                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13874
13875         }
13876
13877         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13878         if( j == backwardMostMove ) i += initialRulePlies;
13879         sprintf(p, "%d ", i);
13880         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13881     }
13882     /* Fullmove number */
13883     sprintf(p, "%d", (move / 2) + 1);
13884     
13885     return StrSave(buf);
13886 }
13887
13888 Boolean
13889 ParseFEN(board, blackPlaysFirst, fen)
13890     Board board;
13891      int *blackPlaysFirst;
13892      char *fen;
13893 {
13894     int i, j;
13895     char *p;
13896     int emptycount;
13897     ChessSquare piece;
13898
13899     p = fen;
13900
13901     /* [HGM] by default clear Crazyhouse holdings, if present */
13902     if(gameInfo.holdingsWidth) {
13903        for(i=0; i<BOARD_HEIGHT; i++) {
13904            board[i][0]             = EmptySquare; /* black holdings */
13905            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13906            board[i][1]             = (ChessSquare) 0; /* black counts */
13907            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13908        }
13909     }
13910
13911     /* Piece placement data */
13912     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13913         j = 0;
13914         for (;;) {
13915             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13916                 if (*p == '/') p++;
13917                 emptycount = gameInfo.boardWidth - j;
13918                 while (emptycount--)
13919                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13920                 break;
13921 #if(BOARD_FILES >= 10)
13922             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13923                 p++; emptycount=10;
13924                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13925                 while (emptycount--)
13926                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13927 #endif
13928             } else if (isdigit(*p)) {
13929                 emptycount = *p++ - '0';
13930                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13931                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13932                 while (emptycount--)
13933                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13934             } else if (*p == '+' || isalpha(*p)) {
13935                 if (j >= gameInfo.boardWidth) return FALSE;
13936                 if(*p=='+') {
13937                     piece = CharToPiece(*++p);
13938                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13939                     piece = (ChessSquare) (PROMOTED piece ); p++;
13940                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13941                 } else piece = CharToPiece(*p++);
13942
13943                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13944                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13945                     piece = (ChessSquare) (PROMOTED piece);
13946                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13947                     p++;
13948                 }
13949                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13950             } else {
13951                 return FALSE;
13952             }
13953         }
13954     }
13955     while (*p == '/' || *p == ' ') p++;
13956
13957     /* [HGM] look for Crazyhouse holdings here */
13958     while(*p==' ') p++;
13959     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13960         if(*p == '[') p++;
13961         if(*p == '-' ) *p++; /* empty holdings */ else {
13962             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13963             /* if we would allow FEN reading to set board size, we would   */
13964             /* have to add holdings and shift the board read so far here   */
13965             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13966                 *p++;
13967                 if((int) piece >= (int) BlackPawn ) {
13968                     i = (int)piece - (int)BlackPawn;
13969                     i = PieceToNumber((ChessSquare)i);
13970                     if( i >= gameInfo.holdingsSize ) return FALSE;
13971                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13972                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13973                 } else {
13974                     i = (int)piece - (int)WhitePawn;
13975                     i = PieceToNumber((ChessSquare)i);
13976                     if( i >= gameInfo.holdingsSize ) return FALSE;
13977                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13978                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13979                 }
13980             }
13981         }
13982         if(*p == ']') *p++;
13983     }
13984
13985     while(*p == ' ') p++;
13986
13987     /* Active color */
13988     switch (*p++) {
13989       case 'w':
13990         *blackPlaysFirst = FALSE;
13991         break;
13992       case 'b': 
13993         *blackPlaysFirst = TRUE;
13994         break;
13995       default:
13996         return FALSE;
13997     }
13998
13999     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14000     /* return the extra info in global variiables             */
14001
14002     /* set defaults in case FEN is incomplete */
14003     board[EP_STATUS] = EP_UNKNOWN;
14004     for(i=0; i<nrCastlingRights; i++ ) {
14005         board[CASTLING][i] =
14006             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14007     }   /* assume possible unless obviously impossible */
14008     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14009     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14010     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14011     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14012     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14013     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14014     FENrulePlies = 0;
14015
14016     while(*p==' ') p++;
14017     if(nrCastlingRights) {
14018       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14019           /* castling indicator present, so default becomes no castlings */
14020           for(i=0; i<nrCastlingRights; i++ ) {
14021                  board[CASTLING][i] = NoRights;
14022           }
14023       }
14024       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14025              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14026              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14027              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14028         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14029
14030         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14031             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14032             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14033         }
14034         switch(c) {
14035           case'K':
14036               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14037               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14038               board[CASTLING][2] = whiteKingFile;
14039               break;
14040           case'Q':
14041               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14042               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14043               board[CASTLING][2] = whiteKingFile;
14044               break;
14045           case'k':
14046               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14047               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14048               board[CASTLING][5] = blackKingFile;
14049               break;
14050           case'q':
14051               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14052               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14053               board[CASTLING][5] = blackKingFile;
14054           case '-':
14055               break;
14056           default: /* FRC castlings */
14057               if(c >= 'a') { /* black rights */
14058                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14059                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14060                   if(i == BOARD_RGHT) break;
14061                   board[CASTLING][5] = i;
14062                   c -= AAA;
14063                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14064                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14065                   if(c > i)
14066                       board[CASTLING][3] = c;
14067                   else
14068                       board[CASTLING][4] = c;
14069               } else { /* white rights */
14070                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14071                     if(board[0][i] == WhiteKing) break;
14072                   if(i == BOARD_RGHT) break;
14073                   board[CASTLING][2] = i;
14074                   c -= AAA - 'a' + 'A';
14075                   if(board[0][c] >= WhiteKing) break;
14076                   if(c > i)
14077                       board[CASTLING][0] = c;
14078                   else
14079                       board[CASTLING][1] = c;
14080               }
14081         }
14082       }
14083     if (appData.debugMode) {
14084         fprintf(debugFP, "FEN castling rights:");
14085         for(i=0; i<nrCastlingRights; i++)
14086         fprintf(debugFP, " %d", board[CASTLING][i]);
14087         fprintf(debugFP, "\n");
14088     }
14089
14090       while(*p==' ') p++;
14091     }
14092
14093     /* read e.p. field in games that know e.p. capture */
14094     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14095        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14096       if(*p=='-') {
14097         p++; board[EP_STATUS] = EP_NONE;
14098       } else {
14099          char c = *p++ - AAA;
14100
14101          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14102          if(*p >= '0' && *p <='9') *p++;
14103          board[EP_STATUS] = c;
14104       }
14105     }
14106
14107
14108     if(sscanf(p, "%d", &i) == 1) {
14109         FENrulePlies = i; /* 50-move ply counter */
14110         /* (The move number is still ignored)    */
14111     }
14112
14113     return TRUE;
14114 }
14115       
14116 void
14117 EditPositionPasteFEN(char *fen)
14118 {
14119   if (fen != NULL) {
14120     Board initial_position;
14121
14122     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14123       DisplayError(_("Bad FEN position in clipboard"), 0);
14124       return ;
14125     } else {
14126       int savedBlackPlaysFirst = blackPlaysFirst;
14127       EditPositionEvent();
14128       blackPlaysFirst = savedBlackPlaysFirst;
14129       CopyBoard(boards[0], initial_position);
14130       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14131       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14132       DisplayBothClocks();
14133       DrawPosition(FALSE, boards[currentMove]);
14134     }
14135   }
14136 }
14137
14138 static char cseq[12] = "\\   ";
14139
14140 Boolean set_cont_sequence(char *new_seq)
14141 {
14142     int len;
14143     Boolean ret;
14144
14145     // handle bad attempts to set the sequence
14146         if (!new_seq)
14147                 return 0; // acceptable error - no debug
14148
14149     len = strlen(new_seq);
14150     ret = (len > 0) && (len < sizeof(cseq));
14151     if (ret)
14152         strcpy(cseq, new_seq);
14153     else if (appData.debugMode)
14154         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14155     return ret;
14156 }
14157
14158 /*
14159     reformat a source message so words don't cross the width boundary.  internal
14160     newlines are not removed.  returns the wrapped size (no null character unless
14161     included in source message).  If dest is NULL, only calculate the size required
14162     for the dest buffer.  lp argument indicats line position upon entry, and it's
14163     passed back upon exit.
14164 */
14165 int wrap(char *dest, char *src, int count, int width, int *lp)
14166 {
14167     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14168
14169     cseq_len = strlen(cseq);
14170     old_line = line = *lp;
14171     ansi = len = clen = 0;
14172
14173     for (i=0; i < count; i++)
14174     {
14175         if (src[i] == '\033')
14176             ansi = 1;
14177
14178         // if we hit the width, back up
14179         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14180         {
14181             // store i & len in case the word is too long
14182             old_i = i, old_len = len;
14183
14184             // find the end of the last word
14185             while (i && src[i] != ' ' && src[i] != '\n')
14186             {
14187                 i--;
14188                 len--;
14189             }
14190
14191             // word too long?  restore i & len before splitting it
14192             if ((old_i-i+clen) >= width)
14193             {
14194                 i = old_i;
14195                 len = old_len;
14196             }
14197
14198             // extra space?
14199             if (i && src[i-1] == ' ')
14200                 len--;
14201
14202             if (src[i] != ' ' && src[i] != '\n')
14203             {
14204                 i--;
14205                 if (len)
14206                     len--;
14207             }
14208
14209             // now append the newline and continuation sequence
14210             if (dest)
14211                 dest[len] = '\n';
14212             len++;
14213             if (dest)
14214                 strncpy(dest+len, cseq, cseq_len);
14215             len += cseq_len;
14216             line = cseq_len;
14217             clen = cseq_len;
14218             continue;
14219         }
14220
14221         if (dest)
14222             dest[len] = src[i];
14223         len++;
14224         if (!ansi)
14225             line++;
14226         if (src[i] == '\n')
14227             line = 0;
14228         if (src[i] == 'm')
14229             ansi = 0;
14230     }
14231     if (dest && appData.debugMode)
14232     {
14233         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14234             count, width, line, len, *lp);
14235         show_bytes(debugFP, src, count);
14236         fprintf(debugFP, "\ndest: ");
14237         show_bytes(debugFP, dest, len);
14238         fprintf(debugFP, "\n");
14239     }
14240     *lp = dest ? line : old_line;
14241
14242     return len;
14243 }
14244
14245 // [HGM] vari: routines for shelving variations
14246
14247 void 
14248 PushTail(int firstMove, int lastMove)
14249 {
14250         int i, j, nrMoves = lastMove - firstMove;
14251
14252         if(appData.icsActive) { // only in local mode
14253                 forwardMostMove = currentMove; // mimic old ICS behavior
14254                 return;
14255         }
14256         if(storedGames >= MAX_VARIATIONS-1) return;
14257
14258         // push current tail of game on stack
14259         savedResult[storedGames] = gameInfo.result;
14260         savedDetails[storedGames] = gameInfo.resultDetails;
14261         gameInfo.resultDetails = NULL;
14262         savedFirst[storedGames] = firstMove;
14263         savedLast [storedGames] = lastMove;
14264         savedFramePtr[storedGames] = framePtr;
14265         framePtr -= nrMoves; // reserve space for the boards
14266         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14267             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14268             for(j=0; j<MOVE_LEN; j++)
14269                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14270             for(j=0; j<2*MOVE_LEN; j++)
14271                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14272             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14273             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14274             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14275             pvInfoList[firstMove+i-1].depth = 0;
14276             commentList[framePtr+i] = commentList[firstMove+i];
14277             commentList[firstMove+i] = NULL;
14278         }
14279
14280         storedGames++;
14281         forwardMostMove = currentMove; // truncte game so we can start variation
14282         if(storedGames == 1) GreyRevert(FALSE);
14283 }
14284
14285 Boolean
14286 PopTail(Boolean annotate)
14287 {
14288         int i, j, nrMoves;
14289         char buf[8000], moveBuf[20];
14290
14291         if(appData.icsActive) return FALSE; // only in local mode
14292         if(!storedGames) return FALSE; // sanity
14293
14294         storedGames--;
14295         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14296         nrMoves = savedLast[storedGames] - currentMove;
14297         if(annotate) {
14298                 int cnt = 10;
14299                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14300                 else strcpy(buf, "(");
14301                 for(i=currentMove; i<forwardMostMove; i++) {
14302                         if(WhiteOnMove(i))
14303                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14304                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14305                         strcat(buf, moveBuf);
14306                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14307                 }
14308                 strcat(buf, ")");
14309         }
14310         for(i=1; i<nrMoves; i++) { // copy last variation back
14311             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14312             for(j=0; j<MOVE_LEN; j++)
14313                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14314             for(j=0; j<2*MOVE_LEN; j++)
14315                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14316             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14317             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14318             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14319             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14320             commentList[currentMove+i] = commentList[framePtr+i];
14321             commentList[framePtr+i] = NULL;
14322         }
14323         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14324         framePtr = savedFramePtr[storedGames];
14325         gameInfo.result = savedResult[storedGames];
14326         if(gameInfo.resultDetails != NULL) {
14327             free(gameInfo.resultDetails);
14328       }
14329         gameInfo.resultDetails = savedDetails[storedGames];
14330         forwardMostMove = currentMove + nrMoves;
14331         if(storedGames == 0) GreyRevert(TRUE);
14332         return TRUE;
14333 }
14334
14335 void 
14336 CleanupTail()
14337 {       // remove all shelved variations
14338         int i;
14339         for(i=0; i<storedGames; i++) {
14340             if(savedDetails[i])
14341                 free(savedDetails[i]);
14342             savedDetails[i] = NULL;
14343         }
14344         for(i=framePtr; i<MAX_MOVES; i++) {
14345                 if(commentList[i]) free(commentList[i]);
14346                 commentList[i] = NULL;
14347         }
14348         framePtr = MAX_MOVES-1;
14349         storedGames = 0;
14350 }