Remove stray dot from WinBoard makefiles
[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 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom: 
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /* 
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0; 
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY       
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707     
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827     
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837     
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000; 
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068     if (appData.debugMode) {
1069         fprintf(debugFP, "%s\n", programVersion);
1070     }
1071
1072     set_cont_sequence(appData.wrapContSeq);
1073     if (appData.matchGames > 0) {
1074         appData.matchMode = TRUE;
1075     } else if (appData.matchMode) {
1076         appData.matchGames = 1;
1077     }
1078     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079         appData.matchGames = appData.sameColorGames;
1080     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083     }
1084     Reset(TRUE, FALSE);
1085     if (appData.noChessProgram || first.protocolVersion == 1) {
1086       InitBackEnd3();
1087     } else {
1088       /* kludge: allow timeout for initial "feature" commands */
1089       FreezeUI();
1090       DisplayMessage("", _("Starting chess program"));
1091       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092     }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098     GameMode initialMode;
1099     char buf[MSG_SIZ];
1100     int err;
1101
1102     InitChessProgram(&first, startedFromSetupPosition);
1103
1104
1105     if (appData.icsActive) {
1106 #ifdef WIN32
1107         /* [DM] Make a console window if needed [HGM] merged ifs */
1108         ConsoleCreate(); 
1109 #endif
1110         err = establish();
1111         if (err != 0) {
1112             if (*appData.icsCommPort != NULLCHAR) {
1113                 sprintf(buf, _("Could not open comm port %s"),  
1114                         appData.icsCommPort);
1115             } else {
1116                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1117                         appData.icsHost, appData.icsPort);
1118             }
1119             DisplayFatalError(buf, err, 1);
1120             return;
1121         }
1122         SetICSMode();
1123         telnetISR =
1124           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125         fromUserISR =
1126           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129     } else if (appData.noChessProgram) {
1130         SetNCPMode();
1131     } else {
1132         SetGNUMode();
1133     }
1134
1135     if (*appData.cmailGameName != NULLCHAR) {
1136         SetCmailMode();
1137         OpenLoopback(&cmailPR);
1138         cmailISR =
1139           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1140     }
1141     
1142     ThawUI();
1143     DisplayMessage("", "");
1144     if (StrCaseCmp(appData.initialMode, "") == 0) {
1145       initialMode = BeginningOfGame;
1146     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147       initialMode = TwoMachinesPlay;
1148     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149       initialMode = AnalyzeFile; 
1150     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151       initialMode = AnalyzeMode;
1152     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153       initialMode = MachinePlaysWhite;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155       initialMode = MachinePlaysBlack;
1156     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157       initialMode = EditGame;
1158     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159       initialMode = EditPosition;
1160     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161       initialMode = Training;
1162     } else {
1163       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164       DisplayFatalError(buf, 0, 2);
1165       return;
1166     }
1167
1168     if (appData.matchMode) {
1169         /* Set up machine vs. machine match */
1170         if (appData.noChessProgram) {
1171             DisplayFatalError(_("Can't have a match with no chess programs"),
1172                               0, 2);
1173             return;
1174         }
1175         matchMode = TRUE;
1176         matchGame = 1;
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             int index = appData.loadGameIndex; // [HGM] autoinc
1179             if(index<0) lastIndex = index = 1;
1180             if (!LoadGameFromFile(appData.loadGameFile,
1181                                   index,
1182                                   appData.loadGameFile, FALSE)) {
1183                 DisplayFatalError(_("Bad game file"), 0, 1);
1184                 return;
1185             }
1186         } else if (*appData.loadPositionFile != NULLCHAR) {
1187             int index = appData.loadPositionIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadPositionFromFile(appData.loadPositionFile,
1190                                       index,
1191                                       appData.loadPositionFile)) {
1192                 DisplayFatalError(_("Bad position file"), 0, 1);
1193                 return;
1194             }
1195         }
1196         TwoMachinesEvent();
1197     } else if (*appData.cmailGameName != NULLCHAR) {
1198         /* Set up cmail mode */
1199         ReloadCmailMsgEvent(TRUE);
1200     } else {
1201         /* Set up other modes */
1202         if (initialMode == AnalyzeFile) {
1203           if (*appData.loadGameFile == NULLCHAR) {
1204             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1205             return;
1206           }
1207         }
1208         if (*appData.loadGameFile != NULLCHAR) {
1209             (void) LoadGameFromFile(appData.loadGameFile,
1210                                     appData.loadGameIndex,
1211                                     appData.loadGameFile, TRUE);
1212         } else if (*appData.loadPositionFile != NULLCHAR) {
1213             (void) LoadPositionFromFile(appData.loadPositionFile,
1214                                         appData.loadPositionIndex,
1215                                         appData.loadPositionFile);
1216             /* [HGM] try to make self-starting even after FEN load */
1217             /* to allow automatic setup of fairy variants with wtm */
1218             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219                 gameMode = BeginningOfGame;
1220                 setboardSpoiledMachineBlack = 1;
1221             }
1222             /* [HGM] loadPos: make that every new game uses the setup */
1223             /* from file as long as we do not switch variant          */
1224             if(!blackPlaysFirst) {
1225                 startedFromPositionFile = TRUE;
1226                 CopyBoard(filePosition, boards[0]);
1227             }
1228         }
1229         if (initialMode == AnalyzeMode) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1236             return;
1237           }
1238           AnalyzeModeEvent();
1239         } else if (initialMode == AnalyzeFile) {
1240           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241           ShowThinkingEvent();
1242           AnalyzeFileEvent();
1243           AnalysisPeriodicEvent(1);
1244         } else if (initialMode == MachinePlaysWhite) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           MachineWhiteEvent();
1256         } else if (initialMode == MachinePlaysBlack) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineBlackEvent();
1268         } else if (initialMode == TwoMachinesPlay) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           TwoMachinesEvent();
1280         } else if (initialMode == EditGame) {
1281           EditGameEvent();
1282         } else if (initialMode == EditPosition) {
1283           EditPositionEvent();
1284         } else if (initialMode == Training) {
1285           if (*appData.loadGameFile == NULLCHAR) {
1286             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1287             return;
1288           }
1289           TrainingEvent();
1290         }
1291     }
1292 }
1293
1294 /*
1295  * Establish will establish a contact to a remote host.port.
1296  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297  *  used to talk to the host.
1298  * Returns 0 if okay, error code if not.
1299  */
1300 int
1301 establish()
1302 {
1303     char buf[MSG_SIZ];
1304
1305     if (*appData.icsCommPort != NULLCHAR) {
1306         /* Talk to the host through a serial comm port */
1307         return OpenCommPort(appData.icsCommPort, &icsPR);
1308
1309     } else if (*appData.gateway != NULLCHAR) {
1310         if (*appData.remoteShell == NULLCHAR) {
1311             /* Use the rcmd protocol to run telnet program on a gateway host */
1312             snprintf(buf, sizeof(buf), "%s %s %s",
1313                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1314             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1315
1316         } else {
1317             /* Use the rsh program to run telnet program on a gateway host */
1318             if (*appData.remoteUser == NULLCHAR) {
1319                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320                         appData.gateway, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             } else {
1323                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324                         appData.remoteShell, appData.gateway, 
1325                         appData.remoteUser, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             }
1328             return StartChildProcess(buf, "", &icsPR);
1329
1330         }
1331     } else if (appData.useTelnet) {
1332         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1333
1334     } else {
1335         /* TCP socket interface differs somewhat between
1336            Unix and NT; handle details in the front end.
1337            */
1338         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1339     }
1340 }
1341
1342 void
1343 show_bytes(fp, buf, count)
1344      FILE *fp;
1345      char *buf;
1346      int count;
1347 {
1348     while (count--) {
1349         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350             fprintf(fp, "\\%03o", *buf & 0xff);
1351         } else {
1352             putc(*buf, fp);
1353         }
1354         buf++;
1355     }
1356     fflush(fp);
1357 }
1358
1359 /* Returns an errno value */
1360 int
1361 OutputMaybeTelnet(pr, message, count, outError)
1362      ProcRef pr;
1363      char *message;
1364      int count;
1365      int *outError;
1366 {
1367     char buf[8192], *p, *q, *buflim;
1368     int left, newcount, outcount;
1369
1370     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371         *appData.gateway != NULLCHAR) {
1372         if (appData.debugMode) {
1373             fprintf(debugFP, ">ICS: ");
1374             show_bytes(debugFP, message, count);
1375             fprintf(debugFP, "\n");
1376         }
1377         return OutputToProcess(pr, message, count, outError);
1378     }
1379
1380     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1381     p = message;
1382     q = buf;
1383     left = count;
1384     newcount = 0;
1385     while (left) {
1386         if (q >= buflim) {
1387             if (appData.debugMode) {
1388                 fprintf(debugFP, ">ICS: ");
1389                 show_bytes(debugFP, buf, newcount);
1390                 fprintf(debugFP, "\n");
1391             }
1392             outcount = OutputToProcess(pr, buf, newcount, outError);
1393             if (outcount < newcount) return -1; /* to be sure */
1394             q = buf;
1395             newcount = 0;
1396         }
1397         if (*p == '\n') {
1398             *q++ = '\r';
1399             newcount++;
1400         } else if (((unsigned char) *p) == TN_IAC) {
1401             *q++ = (char) TN_IAC;
1402             newcount ++;
1403         }
1404         *q++ = *p++;
1405         newcount++;
1406         left--;
1407     }
1408     if (appData.debugMode) {
1409         fprintf(debugFP, ">ICS: ");
1410         show_bytes(debugFP, buf, newcount);
1411         fprintf(debugFP, "\n");
1412     }
1413     outcount = OutputToProcess(pr, buf, newcount, outError);
1414     if (outcount < newcount) return -1; /* to be sure */
1415     return count;
1416 }
1417
1418 void
1419 read_from_player(isr, closure, message, count, error)
1420      InputSourceRef isr;
1421      VOIDSTAR closure;
1422      char *message;
1423      int count;
1424      int error;
1425 {
1426     int outError, outCount;
1427     static int gotEof = 0;
1428
1429     /* Pass data read from player on to ICS */
1430     if (count > 0) {
1431         gotEof = 0;
1432         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433         if (outCount < count) {
1434             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435         }
1436     } else if (count < 0) {
1437         RemoveInputSource(isr);
1438         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439     } else if (gotEof++ > 0) {
1440         RemoveInputSource(isr);
1441         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442     }
1443 }
1444
1445 void
1446 KeepAlive()
1447 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450     SendToICS("date\n");
1451     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1452 }
1453
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1456 {
1457     char buffer[MSG_SIZ];
1458     va_list args;
1459
1460     va_start(args, format);
1461     vsnprintf(buffer, sizeof(buffer), format, args);
1462     buffer[sizeof(buffer)-1] = '\0';
1463     SendToICS(buffer);
1464     va_end(args);
1465 }
1466
1467 void
1468 SendToICS(s)
1469      char *s;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477     if (outCount < count) {
1478         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479     }
1480 }
1481
1482 /* This is used for sending logon scripts to the ICS. Sending
1483    without a delay causes problems when using timestamp on ICC
1484    (at least on my machine). */
1485 void
1486 SendToICSDelayed(s,msdelay)
1487      char *s;
1488      long msdelay;
1489 {
1490     int count, outCount, outError;
1491
1492     if (icsPR == NULL) return;
1493
1494     count = strlen(s);
1495     if (appData.debugMode) {
1496         fprintf(debugFP, ">ICS: ");
1497         show_bytes(debugFP, s, count);
1498         fprintf(debugFP, "\n");
1499     }
1500     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501                                       msdelay);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507
1508 /* Remove all highlighting escape sequences in s
1509    Also deletes any suffix starting with '(' 
1510    */
1511 char *
1512 StripHighlightAndTitle(s)
1513      char *s;
1514 {
1515     static char retbuf[MSG_SIZ];
1516     char *p = retbuf;
1517
1518     while (*s != NULLCHAR) {
1519         while (*s == '\033') {
1520             while (*s != NULLCHAR && !isalpha(*s)) s++;
1521             if (*s != NULLCHAR) s++;
1522         }
1523         while (*s != NULLCHAR && *s != '\033') {
1524             if (*s == '(' || *s == '[') {
1525                 *p = NULLCHAR;
1526                 return retbuf;
1527             }
1528             *p++ = *s++;
1529         }
1530     }
1531     *p = NULLCHAR;
1532     return retbuf;
1533 }
1534
1535 /* Remove all highlighting escape sequences in s */
1536 char *
1537 StripHighlight(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             *p++ = *s++;
1550         }
1551     }
1552     *p = NULLCHAR;
1553     return retbuf;
1554 }
1555
1556 char *variantNames[] = VARIANT_NAMES;
1557 char *
1558 VariantName(v)
1559      VariantClass v;
1560 {
1561     return variantNames[v];
1562 }
1563
1564
1565 /* Identify a variant from the strings the chess servers use or the
1566    PGN Variant tag names we use. */
1567 VariantClass
1568 StringToVariant(e)
1569      char *e;
1570 {
1571     char *p;
1572     int wnum = -1;
1573     VariantClass v = VariantNormal;
1574     int i, found = FALSE;
1575     char buf[MSG_SIZ];
1576
1577     if (!e) return v;
1578
1579     /* [HGM] skip over optional board-size prefixes */
1580     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582         while( *e++ != '_');
1583     }
1584
1585     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1586         v = VariantNormal;
1587         found = TRUE;
1588     } else
1589     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590       if (StrCaseStr(e, variantNames[i])) {
1591         v = (VariantClass) i;
1592         found = TRUE;
1593         break;
1594       }
1595     }
1596
1597     if (!found) {
1598       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599           || StrCaseStr(e, "wild/fr") 
1600           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601         v = VariantFischeRandom;
1602       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603                  (i = 1, p = StrCaseStr(e, "w"))) {
1604         p += i;
1605         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606         if (isdigit(*p)) {
1607           wnum = atoi(p);
1608         } else {
1609           wnum = -1;
1610         }
1611         switch (wnum) {
1612         case 0: /* FICS only, actually */
1613         case 1:
1614           /* Castling legal even if K starts on d-file */
1615           v = VariantWildCastle;
1616           break;
1617         case 2:
1618         case 3:
1619         case 4:
1620           /* Castling illegal even if K & R happen to start in
1621              normal positions. */
1622           v = VariantNoCastle;
1623           break;
1624         case 5:
1625         case 7:
1626         case 8:
1627         case 10:
1628         case 11:
1629         case 12:
1630         case 13:
1631         case 14:
1632         case 15:
1633         case 18:
1634         case 19:
1635           /* Castling legal iff K & R start in normal positions */
1636           v = VariantNormal;
1637           break;
1638         case 6:
1639         case 20:
1640         case 21:
1641           /* Special wilds for position setup; unclear what to do here */
1642           v = VariantLoadable;
1643           break;
1644         case 9:
1645           /* Bizarre ICC game */
1646           v = VariantTwoKings;
1647           break;
1648         case 16:
1649           v = VariantKriegspiel;
1650           break;
1651         case 17:
1652           v = VariantLosers;
1653           break;
1654         case 22:
1655           v = VariantFischeRandom;
1656           break;
1657         case 23:
1658           v = VariantCrazyhouse;
1659           break;
1660         case 24:
1661           v = VariantBughouse;
1662           break;
1663         case 25:
1664           v = Variant3Check;
1665           break;
1666         case 26:
1667           /* Not quite the same as FICS suicide! */
1668           v = VariantGiveaway;
1669           break;
1670         case 27:
1671           v = VariantAtomic;
1672           break;
1673         case 28:
1674           v = VariantShatranj;
1675           break;
1676
1677         /* Temporary names for future ICC types.  The name *will* change in 
1678            the next xboard/WinBoard release after ICC defines it. */
1679         case 29:
1680           v = Variant29;
1681           break;
1682         case 30:
1683           v = Variant30;
1684           break;
1685         case 31:
1686           v = Variant31;
1687           break;
1688         case 32:
1689           v = Variant32;
1690           break;
1691         case 33:
1692           v = Variant33;
1693           break;
1694         case 34:
1695           v = Variant34;
1696           break;
1697         case 35:
1698           v = Variant35;
1699           break;
1700         case 36:
1701           v = Variant36;
1702           break;
1703         case 37:
1704           v = VariantShogi;
1705           break;
1706         case 38:
1707           v = VariantXiangqi;
1708           break;
1709         case 39:
1710           v = VariantCourier;
1711           break;
1712         case 40:
1713           v = VariantGothic;
1714           break;
1715         case 41:
1716           v = VariantCapablanca;
1717           break;
1718         case 42:
1719           v = VariantKnightmate;
1720           break;
1721         case 43:
1722           v = VariantFairy;
1723           break;
1724         case 44:
1725           v = VariantCylinder;
1726           break;
1727         case 45:
1728           v = VariantFalcon;
1729           break;
1730         case 46:
1731           v = VariantCapaRandom;
1732           break;
1733         case 47:
1734           v = VariantBerolina;
1735           break;
1736         case 48:
1737           v = VariantJanus;
1738           break;
1739         case 49:
1740           v = VariantSuper;
1741           break;
1742         case 50:
1743           v = VariantGreat;
1744           break;
1745         case -1:
1746           /* Found "wild" or "w" in the string but no number;
1747              must assume it's normal chess. */
1748           v = VariantNormal;
1749           break;
1750         default:
1751           sprintf(buf, _("Unknown wild type %d"), wnum);
1752           DisplayError(buf, 0);
1753           v = VariantUnknown;
1754           break;
1755         }
1756       }
1757     }
1758     if (appData.debugMode) {
1759       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760               e, wnum, VariantName(v));
1761     }
1762     return v;
1763 }
1764
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1767
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769    advance *index beyond it, and set leftover_start to the new value of
1770    *index; else return FALSE.  If pattern contains the character '*', it
1771    matches any sequence of characters not containing '\r', '\n', or the
1772    character following the '*' (if any), and the matched sequence(s) are
1773    copied into star_match.
1774    */
1775 int
1776 looking_at(buf, index, pattern)
1777      char *buf;
1778      int *index;
1779      char *pattern;
1780 {
1781     char *bufp = &buf[*index], *patternp = pattern;
1782     int star_count = 0;
1783     char *matchp = star_match[0];
1784     
1785     for (;;) {
1786         if (*patternp == NULLCHAR) {
1787             *index = leftover_start = bufp - buf;
1788             *matchp = NULLCHAR;
1789             return TRUE;
1790         }
1791         if (*bufp == NULLCHAR) return FALSE;
1792         if (*patternp == '*') {
1793             if (*bufp == *(patternp + 1)) {
1794                 *matchp = NULLCHAR;
1795                 matchp = star_match[++star_count];
1796                 patternp += 2;
1797                 bufp++;
1798                 continue;
1799             } else if (*bufp == '\n' || *bufp == '\r') {
1800                 patternp++;
1801                 if (*patternp == NULLCHAR)
1802                   continue;
1803                 else
1804                   return FALSE;
1805             } else {
1806                 *matchp++ = *bufp++;
1807                 continue;
1808             }
1809         }
1810         if (*patternp != *bufp) return FALSE;
1811         patternp++;
1812         bufp++;
1813     }
1814 }
1815
1816 void
1817 SendToPlayer(data, length)
1818      char *data;
1819      int length;
1820 {
1821     int error, outCount;
1822     outCount = OutputToProcess(NoProc, data, length, &error);
1823     if (outCount < length) {
1824         DisplayFatalError(_("Error writing to display"), error, 1);
1825     }
1826 }
1827
1828 void
1829 PackHolding(packed, holding)
1830      char packed[];
1831      char *holding;
1832 {
1833     char *p = holding;
1834     char *q = packed;
1835     int runlength = 0;
1836     int curr = 9999;
1837     do {
1838         if (*p == curr) {
1839             runlength++;
1840         } else {
1841             switch (runlength) {
1842               case 0:
1843                 break;
1844               case 1:
1845                 *q++ = curr;
1846                 break;
1847               case 2:
1848                 *q++ = curr;
1849                 *q++ = curr;
1850                 break;
1851               default:
1852                 sprintf(q, "%d", runlength);
1853                 while (*q) q++;
1854                 *q++ = curr;
1855                 break;
1856             }
1857             runlength = 1;
1858             curr = *p;
1859         }
1860     } while (*p++);
1861     *q = NULLCHAR;
1862 }
1863
1864 /* Telnet protocol requests from the front end */
1865 void
1866 TelnetRequest(ddww, option)
1867      unsigned char ddww, option;
1868 {
1869     unsigned char msg[3];
1870     int outCount, outError;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873
1874     if (appData.debugMode) {
1875         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876         switch (ddww) {
1877           case TN_DO:
1878             ddwwStr = "DO";
1879             break;
1880           case TN_DONT:
1881             ddwwStr = "DONT";
1882             break;
1883           case TN_WILL:
1884             ddwwStr = "WILL";
1885             break;
1886           case TN_WONT:
1887             ddwwStr = "WONT";
1888             break;
1889           default:
1890             ddwwStr = buf1;
1891             sprintf(buf1, "%d", ddww);
1892             break;
1893         }
1894         switch (option) {
1895           case TN_ECHO:
1896             optionStr = "ECHO";
1897             break;
1898           default:
1899             optionStr = buf2;
1900             sprintf(buf2, "%d", option);
1901             break;
1902         }
1903         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1904     }
1905     msg[0] = TN_IAC;
1906     msg[1] = ddww;
1907     msg[2] = option;
1908     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909     if (outCount < 3) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 void
1915 DoEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DO, TN_ECHO);
1919 }
1920
1921 void
1922 DontEcho()
1923 {
1924     if (!appData.icsActive) return;
1925     TelnetRequest(TN_DONT, TN_ECHO);
1926 }
1927
1928 void
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 {
1931     /* put the holdings sent to us by the server on the board holdings area */
1932     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1933     char p;
1934     ChessSquare piece;
1935
1936     if(gameInfo.holdingsWidth < 2)  return;
1937     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938         return; // prevent overwriting by pre-board holdings
1939
1940     if( (int)lowestPiece >= BlackPawn ) {
1941         holdingsColumn = 0;
1942         countsColumn = 1;
1943         holdingsStartRow = BOARD_HEIGHT-1;
1944         direction = -1;
1945     } else {
1946         holdingsColumn = BOARD_WIDTH-1;
1947         countsColumn = BOARD_WIDTH-2;
1948         holdingsStartRow = 0;
1949         direction = 1;
1950     }
1951
1952     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953         board[i][holdingsColumn] = EmptySquare;
1954         board[i][countsColumn]   = (ChessSquare) 0;
1955     }
1956     while( (p=*holdings++) != NULLCHAR ) {
1957         piece = CharToPiece( ToUpper(p) );
1958         if(piece == EmptySquare) continue;
1959         /*j = (int) piece - (int) WhitePawn;*/
1960         j = PieceToNumber(piece);
1961         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962         if(j < 0) continue;               /* should not happen */
1963         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965         board[holdingsStartRow+j*direction][countsColumn]++;
1966     }
1967 }
1968
1969
1970 void
1971 VariantSwitch(Board board, VariantClass newVariant)
1972 {
1973    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1974    Board oldBoard;
1975
1976    startedFromPositionFile = FALSE;
1977    if(gameInfo.variant == newVariant) return;
1978
1979    /* [HGM] This routine is called each time an assignment is made to
1980     * gameInfo.variant during a game, to make sure the board sizes
1981     * are set to match the new variant. If that means adding or deleting
1982     * holdings, we shift the playing board accordingly
1983     * This kludge is needed because in ICS observe mode, we get boards
1984     * of an ongoing game without knowing the variant, and learn about the
1985     * latter only later. This can be because of the move list we requested,
1986     * in which case the game history is refilled from the beginning anyway,
1987     * but also when receiving holdings of a crazyhouse game. In the latter
1988     * case we want to add those holdings to the already received position.
1989     */
1990
1991    
1992    if (appData.debugMode) {
1993      fprintf(debugFP, "Switch board from %s to %s\n",
1994              VariantName(gameInfo.variant), VariantName(newVariant));
1995      setbuf(debugFP, NULL);
1996    }
1997    shuffleOpenings = 0;       /* [HGM] shuffle */
1998    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1999    switch(newVariant) 
2000      {
2001      case VariantShogi:
2002        newWidth = 9;  newHeight = 9;
2003        gameInfo.holdingsSize = 7;
2004      case VariantBughouse:
2005      case VariantCrazyhouse:
2006        newHoldingsWidth = 2; break;
2007      case VariantGreat:
2008        newWidth = 10;
2009      case VariantSuper:
2010        newHoldingsWidth = 2;
2011        gameInfo.holdingsSize = 8;
2012        break;
2013      case VariantGothic:
2014      case VariantCapablanca:
2015      case VariantCapaRandom:
2016        newWidth = 10;
2017      default:
2018        newHoldingsWidth = gameInfo.holdingsSize = 0;
2019      };
2020    
2021    if(newWidth  != gameInfo.boardWidth  ||
2022       newHeight != gameInfo.boardHeight ||
2023       newHoldingsWidth != gameInfo.holdingsWidth ) {
2024      
2025      /* shift position to new playing area, if needed */
2026      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027        for(i=0; i<BOARD_HEIGHT; i++) 
2028          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030              board[i][j];
2031        for(i=0; i<newHeight; i++) {
2032          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034        }
2035      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036        for(i=0; i<BOARD_HEIGHT; i++)
2037          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2039              board[i][j];
2040      }
2041      gameInfo.boardWidth  = newWidth;
2042      gameInfo.boardHeight = newHeight;
2043      gameInfo.holdingsWidth = newHoldingsWidth;
2044      gameInfo.variant = newVariant;
2045      InitDrawingSizes(-2, 0);
2046    } else gameInfo.variant = newVariant;
2047    CopyBoard(oldBoard, board);   // remember correctly formatted board
2048      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2049    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2050 }
2051
2052 static int loggedOn = FALSE;
2053
2054 /*-- Game start info cache: --*/
2055 int gs_gamenum;
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\   ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2063
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2066
2067 void
2068 read_from_ics(isr, closure, data, count, error)
2069      InputSourceRef isr;
2070      VOIDSTAR closure;
2071      char *data;
2072      int count;
2073      int error;
2074 {
2075 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2076 #define STARTED_NONE 0
2077 #define STARTED_MOVES 1
2078 #define STARTED_BOARD 2
2079 #define STARTED_OBSERVE 3
2080 #define STARTED_HOLDINGS 4
2081 #define STARTED_CHATTER 5
2082 #define STARTED_COMMENT 6
2083 #define STARTED_MOVES_NOHIDE 7
2084     
2085     static int started = STARTED_NONE;
2086     static char parse[20000];
2087     static int parse_pos = 0;
2088     static char buf[BUF_SIZE + 1];
2089     static int firstTime = TRUE, intfSet = FALSE;
2090     static ColorClass prevColor = ColorNormal;
2091     static int savingComment = FALSE;
2092     static int cmatch = 0; // continuation sequence match
2093     char *bp;
2094     char str[500];
2095     int i, oldi;
2096     int buf_len;
2097     int next_out;
2098     int tkind;
2099     int backup;    /* [DM] For zippy color lines */
2100     char *p;
2101     char talker[MSG_SIZ]; // [HGM] chat
2102     int channel;
2103
2104     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2105
2106     if (appData.debugMode) {
2107       if (!error) {
2108         fprintf(debugFP, "<ICS: ");
2109         show_bytes(debugFP, data, count);
2110         fprintf(debugFP, "\n");
2111       }
2112     }
2113
2114     if (appData.debugMode) { int f = forwardMostMove;
2115         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2116                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2117                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2118     }
2119     if (count > 0) {
2120         /* If last read ended with a partial line that we couldn't parse,
2121            prepend it to the new read and try again. */
2122         if (leftover_len > 0) {
2123             for (i=0; i<leftover_len; i++)
2124               buf[i] = buf[leftover_start + i];
2125         }
2126
2127     /* copy new characters into the buffer */
2128     bp = buf + leftover_len;
2129     buf_len=leftover_len;
2130     for (i=0; i<count; i++)
2131     {
2132         // ignore these
2133         if (data[i] == '\r')
2134             continue;
2135
2136         // join lines split by ICS?
2137         if (!appData.noJoin)
2138         {
2139             /*
2140                 Joining just consists of finding matches against the
2141                 continuation sequence, and discarding that sequence
2142                 if found instead of copying it.  So, until a match
2143                 fails, there's nothing to do since it might be the
2144                 complete sequence, and thus, something we don't want
2145                 copied.
2146             */
2147             if (data[i] == cont_seq[cmatch])
2148             {
2149                 cmatch++;
2150                 if (cmatch == strlen(cont_seq))
2151                 {
2152                     cmatch = 0; // complete match.  just reset the counter
2153
2154                     /*
2155                         it's possible for the ICS to not include the space
2156                         at the end of the last word, making our [correct]
2157                         join operation fuse two separate words.  the server
2158                         does this when the space occurs at the width setting.
2159                     */
2160                     if (!buf_len || buf[buf_len-1] != ' ')
2161                     {
2162                         *bp++ = ' ';
2163                         buf_len++;
2164                     }
2165                 }
2166                 continue;
2167             }
2168             else if (cmatch)
2169             {
2170                 /*
2171                     match failed, so we have to copy what matched before
2172                     falling through and copying this character.  In reality,
2173                     this will only ever be just the newline character, but
2174                     it doesn't hurt to be precise.
2175                 */
2176                 strncpy(bp, cont_seq, cmatch);
2177                 bp += cmatch;
2178                 buf_len += cmatch;
2179                 cmatch = 0;
2180             }
2181         }
2182
2183         // copy this char
2184         *bp++ = data[i];
2185         buf_len++;
2186     }
2187
2188         buf[buf_len] = NULLCHAR;
2189 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2190         next_out = 0;
2191         leftover_start = 0;
2192         
2193         i = 0;
2194         while (i < buf_len) {
2195             /* Deal with part of the TELNET option negotiation
2196                protocol.  We refuse to do anything beyond the
2197                defaults, except that we allow the WILL ECHO option,
2198                which ICS uses to turn off password echoing when we are
2199                directly connected to it.  We reject this option
2200                if localLineEditing mode is on (always on in xboard)
2201                and we are talking to port 23, which might be a real
2202                telnet server that will try to keep WILL ECHO on permanently.
2203              */
2204             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2205                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2206                 unsigned char option;
2207                 oldi = i;
2208                 switch ((unsigned char) buf[++i]) {
2209                   case TN_WILL:
2210                     if (appData.debugMode)
2211                       fprintf(debugFP, "\n<WILL ");
2212                     switch (option = (unsigned char) buf[++i]) {
2213                       case TN_ECHO:
2214                         if (appData.debugMode)
2215                           fprintf(debugFP, "ECHO ");
2216                         /* Reply only if this is a change, according
2217                            to the protocol rules. */
2218                         if (remoteEchoOption) break;
2219                         if (appData.localLineEditing &&
2220                             atoi(appData.icsPort) == TN_PORT) {
2221                             TelnetRequest(TN_DONT, TN_ECHO);
2222                         } else {
2223                             EchoOff();
2224                             TelnetRequest(TN_DO, TN_ECHO);
2225                             remoteEchoOption = TRUE;
2226                         }
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", option);
2231                         /* Whatever this is, we don't want it. */
2232                         TelnetRequest(TN_DONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_WONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<WONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       case TN_ECHO:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "ECHO ");
2243                         /* Reply only if this is a change, according
2244                            to the protocol rules. */
2245                         if (!remoteEchoOption) break;
2246                         EchoOn();
2247                         TelnetRequest(TN_DONT, TN_ECHO);
2248                         remoteEchoOption = FALSE;
2249                         break;
2250                       default:
2251                         if (appData.debugMode)
2252                           fprintf(debugFP, "%d ", (unsigned char) option);
2253                         /* Whatever this is, it must already be turned
2254                            off, because we never agree to turn on
2255                            anything non-default, so according to the
2256                            protocol rules, we don't reply. */
2257                         break;
2258                     }
2259                     break;
2260                   case TN_DO:
2261                     if (appData.debugMode)
2262                       fprintf(debugFP, "\n<DO ");
2263                     switch (option = (unsigned char) buf[++i]) {
2264                       default:
2265                         /* Whatever this is, we refuse to do it. */
2266                         if (appData.debugMode)
2267                           fprintf(debugFP, "%d ", option);
2268                         TelnetRequest(TN_WONT, option);
2269                         break;
2270                     }
2271                     break;
2272                   case TN_DONT:
2273                     if (appData.debugMode)
2274                       fprintf(debugFP, "\n<DONT ");
2275                     switch (option = (unsigned char) buf[++i]) {
2276                       default:
2277                         if (appData.debugMode)
2278                           fprintf(debugFP, "%d ", option);
2279                         /* Whatever this is, we are already not doing
2280                            it, because we never agree to do anything
2281                            non-default, so according to the protocol
2282                            rules, we don't reply. */
2283                         break;
2284                     }
2285                     break;
2286                   case TN_IAC:
2287                     if (appData.debugMode)
2288                       fprintf(debugFP, "\n<IAC ");
2289                     /* Doubled IAC; pass it through */
2290                     i--;
2291                     break;
2292                   default:
2293                     if (appData.debugMode)
2294                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2295                     /* Drop all other telnet commands on the floor */
2296                     break;
2297                 }
2298                 if (oldi > next_out)
2299                   SendToPlayer(&buf[next_out], oldi - next_out);
2300                 if (++i > next_out)
2301                   next_out = i;
2302                 continue;
2303             }
2304                 
2305             /* OK, this at least will *usually* work */
2306             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2307                 loggedOn = TRUE;
2308             }
2309             
2310             if (loggedOn && !intfSet) {
2311                 if (ics_type == ICS_ICC) {
2312                   sprintf(str,
2313                           "/set-quietly interface %s\n/set-quietly style 12\n",
2314                           programVersion);
2315                 } else if (ics_type == ICS_CHESSNET) {
2316                   sprintf(str, "/style 12\n");
2317                 } else {
2318                   strcpy(str, "alias $ @\n$set interface ");
2319                   strcat(str, programVersion);
2320                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2321 #ifdef WIN32
2322                   strcat(str, "$iset nohighlight 1\n");
2323 #endif
2324                   strcat(str, "$iset lock 1\n$style 12\n");
2325                 }
2326                 SendToICS(str);
2327                 NotifyFrontendLogin();
2328                 intfSet = TRUE;
2329             }
2330
2331             if (started == STARTED_COMMENT) {
2332                 /* Accumulate characters in comment */
2333                 parse[parse_pos++] = buf[i];
2334                 if (buf[i] == '\n') {
2335                     parse[parse_pos] = NULLCHAR;
2336                     if(chattingPartner>=0) {
2337                         char mess[MSG_SIZ];
2338                         sprintf(mess, "%s%s", talker, parse);
2339                         OutputChatMessage(chattingPartner, mess);
2340                         chattingPartner = -1;
2341                     } else
2342                     if(!suppressKibitz) // [HGM] kibitz
2343                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2344                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2345                         int nrDigit = 0, nrAlph = 0, j;
2346                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2347                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2348                         parse[parse_pos] = NULLCHAR;
2349                         // try to be smart: if it does not look like search info, it should go to
2350                         // ICS interaction window after all, not to engine-output window.
2351                         for(j=0; j<parse_pos; j++) { // count letters and digits
2352                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2353                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2354                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2355                         }
2356                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2357                             int depth=0; float score;
2358                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2359                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2360                                 pvInfoList[forwardMostMove-1].depth = depth;
2361                                 pvInfoList[forwardMostMove-1].score = 100*score;
2362                             }
2363                             OutputKibitz(suppressKibitz, parse);
2364                             next_out = i+1; // [HGM] suppress printing in ICS window
2365                         } else {
2366                             char tmp[MSG_SIZ];
2367                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2368                             SendToPlayer(tmp, strlen(tmp));
2369                         }
2370                     }
2371                     started = STARTED_NONE;
2372                 } else {
2373                     /* Don't match patterns against characters in comment */
2374                     i++;
2375                     continue;
2376                 }
2377             }
2378             if (started == STARTED_CHATTER) {
2379                 if (buf[i] != '\n') {
2380                     /* Don't match patterns against characters in chatter */
2381                     i++;
2382                     continue;
2383                 }
2384                 started = STARTED_NONE;
2385             }
2386
2387             /* Kludge to deal with rcmd protocol */
2388             if (firstTime && looking_at(buf, &i, "\001*")) {
2389                 DisplayFatalError(&buf[1], 0, 1);
2390                 continue;
2391             } else {
2392                 firstTime = FALSE;
2393             }
2394
2395             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2396                 ics_type = ICS_ICC;
2397                 ics_prefix = "/";
2398                 if (appData.debugMode)
2399                   fprintf(debugFP, "ics_type %d\n", ics_type);
2400                 continue;
2401             }
2402             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2403                 ics_type = ICS_FICS;
2404                 ics_prefix = "$";
2405                 if (appData.debugMode)
2406                   fprintf(debugFP, "ics_type %d\n", ics_type);
2407                 continue;
2408             }
2409             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2410                 ics_type = ICS_CHESSNET;
2411                 ics_prefix = "/";
2412                 if (appData.debugMode)
2413                   fprintf(debugFP, "ics_type %d\n", ics_type);
2414                 continue;
2415             }
2416
2417             if (!loggedOn &&
2418                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2419                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2420                  looking_at(buf, &i, "will be \"*\""))) {
2421               strcpy(ics_handle, star_match[0]);
2422               continue;
2423             }
2424
2425             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2426               char buf[MSG_SIZ];
2427               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2428               DisplayIcsInteractionTitle(buf);
2429               have_set_title = TRUE;
2430             }
2431
2432             /* skip finger notes */
2433             if (started == STARTED_NONE &&
2434                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2435                  (buf[i] == '1' && buf[i+1] == '0')) &&
2436                 buf[i+2] == ':' && buf[i+3] == ' ') {
2437               started = STARTED_CHATTER;
2438               i += 3;
2439               continue;
2440             }
2441
2442             /* skip formula vars */
2443             if (started == STARTED_NONE &&
2444                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2445               started = STARTED_CHATTER;
2446               i += 3;
2447               continue;
2448             }
2449
2450             oldi = i;
2451             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2452             if (appData.autoKibitz && started == STARTED_NONE && 
2453                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2454                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2455                 if(looking_at(buf, &i, "* kibitzes: ") &&
2456                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2457                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2458                         suppressKibitz = TRUE;
2459                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2460                                 && (gameMode == IcsPlayingWhite)) ||
2461                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2462                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2463                             started = STARTED_CHATTER; // own kibitz we simply discard
2464                         else {
2465                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2466                             parse_pos = 0; parse[0] = NULLCHAR;
2467                             savingComment = TRUE;
2468                             suppressKibitz = gameMode != IcsObserving ? 2 :
2469                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2470                         } 
2471                         continue;
2472                 } else
2473                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2474                     // suppress the acknowledgements of our own autoKibitz
2475                     SendToPlayer(star_match[0], strlen(star_match[0]));
2476                     looking_at(buf, &i, "*% "); // eat prompt
2477                     next_out = i;
2478                 }
2479             } // [HGM] kibitz: end of patch
2480
2481 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2482
2483             // [HGM] chat: intercept tells by users for which we have an open chat window
2484             channel = -1;
2485             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2486                                            looking_at(buf, &i, "* whispers:") ||
2487                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2488                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2489                 int p;
2490                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2491                 chattingPartner = -1;
2492
2493                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2494                 for(p=0; p<MAX_CHAT; p++) {
2495                     if(channel == atoi(chatPartner[p])) {
2496                     talker[0] = '['; strcat(talker, "]");
2497                     chattingPartner = p; break;
2498                     }
2499                 } else
2500                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2501                 for(p=0; p<MAX_CHAT; p++) {
2502                     if(!strcmp("WHISPER", chatPartner[p])) {
2503                         talker[0] = '['; strcat(talker, "]");
2504                         chattingPartner = p; break;
2505                     }
2506                 }
2507                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2508                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2509                     talker[0] = 0;
2510                     chattingPartner = p; break;
2511                 }
2512                 if(chattingPartner<0) i = oldi; else {
2513                     started = STARTED_COMMENT;
2514                     parse_pos = 0; parse[0] = NULLCHAR;
2515                     savingComment = TRUE;
2516                     suppressKibitz = TRUE;
2517                 }
2518             } // [HGM] chat: end of patch
2519
2520             if (appData.zippyTalk || appData.zippyPlay) {
2521                 /* [DM] Backup address for color zippy lines */
2522                 backup = i;
2523 #if ZIPPY
2524        #ifdef WIN32
2525                if (loggedOn == TRUE)
2526                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2527                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2528        #else
2529                 if (ZippyControl(buf, &i) ||
2530                     ZippyConverse(buf, &i) ||
2531                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2532                       loggedOn = TRUE;
2533                       if (!appData.colorize) continue;
2534                 }
2535        #endif
2536 #endif
2537             } // [DM] 'else { ' deleted
2538                 if (
2539                     /* Regular tells and says */
2540                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2541                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2542                     looking_at(buf, &i, "* says: ") ||
2543                     /* Don't color "message" or "messages" output */
2544                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2545                     looking_at(buf, &i, "*. * at *:*: ") ||
2546                     looking_at(buf, &i, "--* (*:*): ") ||
2547                     /* Message notifications (same color as tells) */
2548                     looking_at(buf, &i, "* has left a message ") ||
2549                     looking_at(buf, &i, "* just sent you a message:\n") ||
2550                     /* Whispers and kibitzes */
2551                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2552                     looking_at(buf, &i, "* kibitzes: ") ||
2553                     /* Channel tells */
2554                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2555
2556                   if (tkind == 1 && strchr(star_match[0], ':')) {
2557                       /* Avoid "tells you:" spoofs in channels */
2558                      tkind = 3;
2559                   }
2560                   if (star_match[0][0] == NULLCHAR ||
2561                       strchr(star_match[0], ' ') ||
2562                       (tkind == 3 && strchr(star_match[1], ' '))) {
2563                     /* Reject bogus matches */
2564                     i = oldi;
2565                   } else {
2566                     if (appData.colorize) {
2567                       if (oldi > next_out) {
2568                         SendToPlayer(&buf[next_out], oldi - next_out);
2569                         next_out = oldi;
2570                       }
2571                       switch (tkind) {
2572                       case 1:
2573                         Colorize(ColorTell, FALSE);
2574                         curColor = ColorTell;
2575                         break;
2576                       case 2:
2577                         Colorize(ColorKibitz, FALSE);
2578                         curColor = ColorKibitz;
2579                         break;
2580                       case 3:
2581                         p = strrchr(star_match[1], '(');
2582                         if (p == NULL) {
2583                           p = star_match[1];
2584                         } else {
2585                           p++;
2586                         }
2587                         if (atoi(p) == 1) {
2588                           Colorize(ColorChannel1, FALSE);
2589                           curColor = ColorChannel1;
2590                         } else {
2591                           Colorize(ColorChannel, FALSE);
2592                           curColor = ColorChannel;
2593                         }
2594                         break;
2595                       case 5:
2596                         curColor = ColorNormal;
2597                         break;
2598                       }
2599                     }
2600                     if (started == STARTED_NONE && appData.autoComment &&
2601                         (gameMode == IcsObserving ||
2602                          gameMode == IcsPlayingWhite ||
2603                          gameMode == IcsPlayingBlack)) {
2604                       parse_pos = i - oldi;
2605                       memcpy(parse, &buf[oldi], parse_pos);
2606                       parse[parse_pos] = NULLCHAR;
2607                       started = STARTED_COMMENT;
2608                       savingComment = TRUE;
2609                     } else {
2610                       started = STARTED_CHATTER;
2611                       savingComment = FALSE;
2612                     }
2613                     loggedOn = TRUE;
2614                     continue;
2615                   }
2616                 }
2617
2618                 if (looking_at(buf, &i, "* s-shouts: ") ||
2619                     looking_at(buf, &i, "* c-shouts: ")) {
2620                     if (appData.colorize) {
2621                         if (oldi > next_out) {
2622                             SendToPlayer(&buf[next_out], oldi - next_out);
2623                             next_out = oldi;
2624                         }
2625                         Colorize(ColorSShout, FALSE);
2626                         curColor = ColorSShout;
2627                     }
2628                     loggedOn = TRUE;
2629                     started = STARTED_CHATTER;
2630                     continue;
2631                 }
2632
2633                 if (looking_at(buf, &i, "--->")) {
2634                     loggedOn = TRUE;
2635                     continue;
2636                 }
2637
2638                 if (looking_at(buf, &i, "* shouts: ") ||
2639                     looking_at(buf, &i, "--> ")) {
2640                     if (appData.colorize) {
2641                         if (oldi > next_out) {
2642                             SendToPlayer(&buf[next_out], oldi - next_out);
2643                             next_out = oldi;
2644                         }
2645                         Colorize(ColorShout, FALSE);
2646                         curColor = ColorShout;
2647                     }
2648                     loggedOn = TRUE;
2649                     started = STARTED_CHATTER;
2650                     continue;
2651                 }
2652
2653                 if (looking_at( buf, &i, "Challenge:")) {
2654                     if (appData.colorize) {
2655                         if (oldi > next_out) {
2656                             SendToPlayer(&buf[next_out], oldi - next_out);
2657                             next_out = oldi;
2658                         }
2659                         Colorize(ColorChallenge, FALSE);
2660                         curColor = ColorChallenge;
2661                     }
2662                     loggedOn = TRUE;
2663                     continue;
2664                 }
2665
2666                 if (looking_at(buf, &i, "* offers you") ||
2667                     looking_at(buf, &i, "* offers to be") ||
2668                     looking_at(buf, &i, "* would like to") ||
2669                     looking_at(buf, &i, "* requests to") ||
2670                     looking_at(buf, &i, "Your opponent offers") ||
2671                     looking_at(buf, &i, "Your opponent requests")) {
2672
2673                     if (appData.colorize) {
2674                         if (oldi > next_out) {
2675                             SendToPlayer(&buf[next_out], oldi - next_out);
2676                             next_out = oldi;
2677                         }
2678                         Colorize(ColorRequest, FALSE);
2679                         curColor = ColorRequest;
2680                     }
2681                     continue;
2682                 }
2683
2684                 if (looking_at(buf, &i, "* (*) seeking")) {
2685                     if (appData.colorize) {
2686                         if (oldi > next_out) {
2687                             SendToPlayer(&buf[next_out], oldi - next_out);
2688                             next_out = oldi;
2689                         }
2690                         Colorize(ColorSeek, FALSE);
2691                         curColor = ColorSeek;
2692                     }
2693                     continue;
2694             }
2695
2696             if (looking_at(buf, &i, "\\   ")) {
2697                 if (prevColor != ColorNormal) {
2698                     if (oldi > next_out) {
2699                         SendToPlayer(&buf[next_out], oldi - next_out);
2700                         next_out = oldi;
2701                     }
2702                     Colorize(prevColor, TRUE);
2703                     curColor = prevColor;
2704                 }
2705                 if (savingComment) {
2706                     parse_pos = i - oldi;
2707                     memcpy(parse, &buf[oldi], parse_pos);
2708                     parse[parse_pos] = NULLCHAR;
2709                     started = STARTED_COMMENT;
2710                 } else {
2711                     started = STARTED_CHATTER;
2712                 }
2713                 continue;
2714             }
2715
2716             if (looking_at(buf, &i, "Black Strength :") ||
2717                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2718                 looking_at(buf, &i, "<10>") ||
2719                 looking_at(buf, &i, "#@#")) {
2720                 /* Wrong board style */
2721                 loggedOn = TRUE;
2722                 SendToICS(ics_prefix);
2723                 SendToICS("set style 12\n");
2724                 SendToICS(ics_prefix);
2725                 SendToICS("refresh\n");
2726                 continue;
2727             }
2728             
2729             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2730                 ICSInitScript();
2731                 have_sent_ICS_logon = 1;
2732                 continue;
2733             }
2734               
2735             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2736                 (looking_at(buf, &i, "\n<12> ") ||
2737                  looking_at(buf, &i, "<12> "))) {
2738                 loggedOn = TRUE;
2739                 if (oldi > next_out) {
2740                     SendToPlayer(&buf[next_out], oldi - next_out);
2741                 }
2742                 next_out = i;
2743                 started = STARTED_BOARD;
2744                 parse_pos = 0;
2745                 continue;
2746             }
2747
2748             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2749                 looking_at(buf, &i, "<b1> ")) {
2750                 if (oldi > next_out) {
2751                     SendToPlayer(&buf[next_out], oldi - next_out);
2752                 }
2753                 next_out = i;
2754                 started = STARTED_HOLDINGS;
2755                 parse_pos = 0;
2756                 continue;
2757             }
2758
2759             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2760                 loggedOn = TRUE;
2761                 /* Header for a move list -- first line */
2762
2763                 switch (ics_getting_history) {
2764                   case H_FALSE:
2765                     switch (gameMode) {
2766                       case IcsIdle:
2767                       case BeginningOfGame:
2768                         /* User typed "moves" or "oldmoves" while we
2769                            were idle.  Pretend we asked for these
2770                            moves and soak them up so user can step
2771                            through them and/or save them.
2772                            */
2773                         Reset(FALSE, TRUE);
2774                         gameMode = IcsObserving;
2775                         ModeHighlight();
2776                         ics_gamenum = -1;
2777                         ics_getting_history = H_GOT_UNREQ_HEADER;
2778                         break;
2779                       case EditGame: /*?*/
2780                       case EditPosition: /*?*/
2781                         /* Should above feature work in these modes too? */
2782                         /* For now it doesn't */
2783                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2784                         break;
2785                       default:
2786                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2787                         break;
2788                     }
2789                     break;
2790                   case H_REQUESTED:
2791                     /* Is this the right one? */
2792                     if (gameInfo.white && gameInfo.black &&
2793                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2794                         strcmp(gameInfo.black, star_match[2]) == 0) {
2795                         /* All is well */
2796                         ics_getting_history = H_GOT_REQ_HEADER;
2797                     }
2798                     break;
2799                   case H_GOT_REQ_HEADER:
2800                   case H_GOT_UNREQ_HEADER:
2801                   case H_GOT_UNWANTED_HEADER:
2802                   case H_GETTING_MOVES:
2803                     /* Should not happen */
2804                     DisplayError(_("Error gathering move list: two headers"), 0);
2805                     ics_getting_history = H_FALSE;
2806                     break;
2807                 }
2808
2809                 /* Save player ratings into gameInfo if needed */
2810                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2811                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2812                     (gameInfo.whiteRating == -1 ||
2813                      gameInfo.blackRating == -1)) {
2814
2815                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2816                     gameInfo.blackRating = string_to_rating(star_match[3]);
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2819                               gameInfo.whiteRating, gameInfo.blackRating);
2820                 }
2821                 continue;
2822             }
2823
2824             if (looking_at(buf, &i,
2825               "* * match, initial time: * minute*, increment: * second")) {
2826                 /* Header for a move list -- second line */
2827                 /* Initial board will follow if this is a wild game */
2828                 if (gameInfo.event != NULL) free(gameInfo.event);
2829                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2830                 gameInfo.event = StrSave(str);
2831                 /* [HGM] we switched variant. Translate boards if needed. */
2832                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2833                 continue;
2834             }
2835
2836             if (looking_at(buf, &i, "Move  ")) {
2837                 /* Beginning of a move list */
2838                 switch (ics_getting_history) {
2839                   case H_FALSE:
2840                     /* Normally should not happen */
2841                     /* Maybe user hit reset while we were parsing */
2842                     break;
2843                   case H_REQUESTED:
2844                     /* Happens if we are ignoring a move list that is not
2845                      * the one we just requested.  Common if the user
2846                      * tries to observe two games without turning off
2847                      * getMoveList */
2848                     break;
2849                   case H_GETTING_MOVES:
2850                     /* Should not happen */
2851                     DisplayError(_("Error gathering move list: nested"), 0);
2852                     ics_getting_history = H_FALSE;
2853                     break;
2854                   case H_GOT_REQ_HEADER:
2855                     ics_getting_history = H_GETTING_MOVES;
2856                     started = STARTED_MOVES;
2857                     parse_pos = 0;
2858                     if (oldi > next_out) {
2859                         SendToPlayer(&buf[next_out], oldi - next_out);
2860                     }
2861                     break;
2862                   case H_GOT_UNREQ_HEADER:
2863                     ics_getting_history = H_GETTING_MOVES;
2864                     started = STARTED_MOVES_NOHIDE;
2865                     parse_pos = 0;
2866                     break;
2867                   case H_GOT_UNWANTED_HEADER:
2868                     ics_getting_history = H_FALSE;
2869                     break;
2870                 }
2871                 continue;
2872             }                           
2873             
2874             if (looking_at(buf, &i, "% ") ||
2875                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2876                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2877                 if(suppressKibitz) next_out = i;
2878                 savingComment = FALSE;
2879                 suppressKibitz = 0;
2880                 switch (started) {
2881                   case STARTED_MOVES:
2882                   case STARTED_MOVES_NOHIDE:
2883                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2884                     parse[parse_pos + i - oldi] = NULLCHAR;
2885                     ParseGameHistory(parse);
2886 #if ZIPPY
2887                     if (appData.zippyPlay && first.initDone) {
2888                         FeedMovesToProgram(&first, forwardMostMove);
2889                         if (gameMode == IcsPlayingWhite) {
2890                             if (WhiteOnMove(forwardMostMove)) {
2891                                 if (first.sendTime) {
2892                                   if (first.useColors) {
2893                                     SendToProgram("black\n", &first); 
2894                                   }
2895                                   SendTimeRemaining(&first, TRUE);
2896                                 }
2897                                 if (first.useColors) {
2898                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2899                                 }
2900                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2901                                 first.maybeThinking = TRUE;
2902                             } else {
2903                                 if (first.usePlayother) {
2904                                   if (first.sendTime) {
2905                                     SendTimeRemaining(&first, TRUE);
2906                                   }
2907                                   SendToProgram("playother\n", &first);
2908                                   firstMove = FALSE;
2909                                 } else {
2910                                   firstMove = TRUE;
2911                                 }
2912                             }
2913                         } else if (gameMode == IcsPlayingBlack) {
2914                             if (!WhiteOnMove(forwardMostMove)) {
2915                                 if (first.sendTime) {
2916                                   if (first.useColors) {
2917                                     SendToProgram("white\n", &first);
2918                                   }
2919                                   SendTimeRemaining(&first, FALSE);
2920                                 }
2921                                 if (first.useColors) {
2922                                   SendToProgram("black\n", &first);
2923                                 }
2924                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2925                                 first.maybeThinking = TRUE;
2926                             } else {
2927                                 if (first.usePlayother) {
2928                                   if (first.sendTime) {
2929                                     SendTimeRemaining(&first, FALSE);
2930                                   }
2931                                   SendToProgram("playother\n", &first);
2932                                   firstMove = FALSE;
2933                                 } else {
2934                                   firstMove = TRUE;
2935                                 }
2936                             }
2937                         }                       
2938                     }
2939 #endif
2940                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2941                         /* Moves came from oldmoves or moves command
2942                            while we weren't doing anything else.
2943                            */
2944                         currentMove = forwardMostMove;
2945                         ClearHighlights();/*!!could figure this out*/
2946                         flipView = appData.flipView;
2947                         DrawPosition(TRUE, boards[currentMove]);
2948                         DisplayBothClocks();
2949                         sprintf(str, "%s vs. %s",
2950                                 gameInfo.white, gameInfo.black);
2951                         DisplayTitle(str);
2952                         gameMode = IcsIdle;
2953                     } else {
2954                         /* Moves were history of an active game */
2955                         if (gameInfo.resultDetails != NULL) {
2956                             free(gameInfo.resultDetails);
2957                             gameInfo.resultDetails = NULL;
2958                         }
2959                     }
2960                     HistorySet(parseList, backwardMostMove,
2961                                forwardMostMove, currentMove-1);
2962                     DisplayMove(currentMove - 1);
2963                     if (started == STARTED_MOVES) next_out = i;
2964                     started = STARTED_NONE;
2965                     ics_getting_history = H_FALSE;
2966                     break;
2967
2968                   case STARTED_OBSERVE:
2969                     started = STARTED_NONE;
2970                     SendToICS(ics_prefix);
2971                     SendToICS("refresh\n");
2972                     break;
2973
2974                   default:
2975                     break;
2976                 }
2977                 if(bookHit) { // [HGM] book: simulate book reply
2978                     static char bookMove[MSG_SIZ]; // a bit generous?
2979
2980                     programStats.nodes = programStats.depth = programStats.time = 
2981                     programStats.score = programStats.got_only_move = 0;
2982                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2983
2984                     strcpy(bookMove, "move ");
2985                     strcat(bookMove, bookHit);
2986                     HandleMachineMove(bookMove, &first);
2987                 }
2988                 continue;
2989             }
2990             
2991             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2992                  started == STARTED_HOLDINGS ||
2993                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2994                 /* Accumulate characters in move list or board */
2995                 parse[parse_pos++] = buf[i];
2996             }
2997             
2998             /* Start of game messages.  Mostly we detect start of game
2999                when the first board image arrives.  On some versions
3000                of the ICS, though, we need to do a "refresh" after starting
3001                to observe in order to get the current board right away. */
3002             if (looking_at(buf, &i, "Adding game * to observation list")) {
3003                 started = STARTED_OBSERVE;
3004                 continue;
3005             }
3006
3007             /* Handle auto-observe */
3008             if (appData.autoObserve &&
3009                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3010                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3011                 char *player;
3012                 /* Choose the player that was highlighted, if any. */
3013                 if (star_match[0][0] == '\033' ||
3014                     star_match[1][0] != '\033') {
3015                     player = star_match[0];
3016                 } else {
3017                     player = star_match[2];
3018                 }
3019                 sprintf(str, "%sobserve %s\n",
3020                         ics_prefix, StripHighlightAndTitle(player));
3021                 SendToICS(str);
3022
3023                 /* Save ratings from notify string */
3024                 strcpy(player1Name, star_match[0]);
3025                 player1Rating = string_to_rating(star_match[1]);
3026                 strcpy(player2Name, star_match[2]);
3027                 player2Rating = string_to_rating(star_match[3]);
3028
3029                 if (appData.debugMode)
3030                   fprintf(debugFP, 
3031                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3032                           player1Name, player1Rating,
3033                           player2Name, player2Rating);
3034
3035                 continue;
3036             }
3037
3038             /* Deal with automatic examine mode after a game,
3039                and with IcsObserving -> IcsExamining transition */
3040             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3041                 looking_at(buf, &i, "has made you an examiner of game *")) {
3042
3043                 int gamenum = atoi(star_match[0]);
3044                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3045                     gamenum == ics_gamenum) {
3046                     /* We were already playing or observing this game;
3047                        no need to refetch history */
3048                     gameMode = IcsExamining;
3049                     if (pausing) {
3050                         pauseExamForwardMostMove = forwardMostMove;
3051                     } else if (currentMove < forwardMostMove) {
3052                         ForwardInner(forwardMostMove);
3053                     }
3054                 } else {
3055                     /* I don't think this case really can happen */
3056                     SendToICS(ics_prefix);
3057                     SendToICS("refresh\n");
3058                 }
3059                 continue;
3060             }    
3061             
3062             /* Error messages */
3063 //          if (ics_user_moved) {
3064             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3065                 if (looking_at(buf, &i, "Illegal move") ||
3066                     looking_at(buf, &i, "Not a legal move") ||
3067                     looking_at(buf, &i, "Your king is in check") ||
3068                     looking_at(buf, &i, "It isn't your turn") ||
3069                     looking_at(buf, &i, "It is not your move")) {
3070                     /* Illegal move */
3071                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3072                         currentMove = --forwardMostMove;
3073                         DisplayMove(currentMove - 1); /* before DMError */
3074                         DrawPosition(FALSE, boards[currentMove]);
3075                         SwitchClocks();
3076                         DisplayBothClocks();
3077                     }
3078                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3079                     ics_user_moved = 0;
3080                     continue;
3081                 }
3082             }
3083
3084             if (looking_at(buf, &i, "still have time") ||
3085                 looking_at(buf, &i, "not out of time") ||
3086                 looking_at(buf, &i, "either player is out of time") ||
3087                 looking_at(buf, &i, "has timeseal; checking")) {
3088                 /* We must have called his flag a little too soon */
3089                 whiteFlag = blackFlag = FALSE;
3090                 continue;
3091             }
3092
3093             if (looking_at(buf, &i, "added * seconds to") ||
3094                 looking_at(buf, &i, "seconds were added to")) {
3095                 /* Update the clocks */
3096                 SendToICS(ics_prefix);
3097                 SendToICS("refresh\n");
3098                 continue;
3099             }
3100
3101             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3102                 ics_clock_paused = TRUE;
3103                 StopClocks();
3104                 continue;
3105             }
3106
3107             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3108                 ics_clock_paused = FALSE;
3109                 StartClocks();
3110                 continue;
3111             }
3112
3113             /* Grab player ratings from the Creating: message.
3114                Note we have to check for the special case when
3115                the ICS inserts things like [white] or [black]. */
3116             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3117                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3118                 /* star_matches:
3119                    0    player 1 name (not necessarily white)
3120                    1    player 1 rating
3121                    2    empty, white, or black (IGNORED)
3122                    3    player 2 name (not necessarily black)
3123                    4    player 2 rating
3124                    
3125                    The names/ratings are sorted out when the game
3126                    actually starts (below).
3127                 */
3128                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3129                 player1Rating = string_to_rating(star_match[1]);
3130                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3131                 player2Rating = string_to_rating(star_match[4]);
3132
3133                 if (appData.debugMode)
3134                   fprintf(debugFP, 
3135                           "Ratings from 'Creating:' %s %d, %s %d\n",
3136                           player1Name, player1Rating,
3137                           player2Name, player2Rating);
3138
3139                 continue;
3140             }
3141             
3142             /* Improved generic start/end-of-game messages */
3143             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3144                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3145                 /* If tkind == 0: */
3146                 /* star_match[0] is the game number */
3147                 /*           [1] is the white player's name */
3148                 /*           [2] is the black player's name */
3149                 /* For end-of-game: */
3150                 /*           [3] is the reason for the game end */
3151                 /*           [4] is a PGN end game-token, preceded by " " */
3152                 /* For start-of-game: */
3153                 /*           [3] begins with "Creating" or "Continuing" */
3154                 /*           [4] is " *" or empty (don't care). */
3155                 int gamenum = atoi(star_match[0]);
3156                 char *whitename, *blackname, *why, *endtoken;
3157                 ChessMove endtype = (ChessMove) 0;
3158
3159                 if (tkind == 0) {
3160                   whitename = star_match[1];
3161                   blackname = star_match[2];
3162                   why = star_match[3];
3163                   endtoken = star_match[4];
3164                 } else {
3165                   whitename = star_match[1];
3166                   blackname = star_match[3];
3167                   why = star_match[5];
3168                   endtoken = star_match[6];
3169                 }
3170
3171                 /* Game start messages */
3172                 if (strncmp(why, "Creating ", 9) == 0 ||
3173                     strncmp(why, "Continuing ", 11) == 0) {
3174                     gs_gamenum = gamenum;
3175                     strcpy(gs_kind, strchr(why, ' ') + 1);
3176                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3177 #if ZIPPY
3178                     if (appData.zippyPlay) {
3179                         ZippyGameStart(whitename, blackname);
3180                     }
3181 #endif /*ZIPPY*/
3182                     continue;
3183                 }
3184
3185                 /* Game end messages */
3186                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3187                     ics_gamenum != gamenum) {
3188                     continue;
3189                 }
3190                 while (endtoken[0] == ' ') endtoken++;
3191                 switch (endtoken[0]) {
3192                   case '*':
3193                   default:
3194                     endtype = GameUnfinished;
3195                     break;
3196                   case '0':
3197                     endtype = BlackWins;
3198                     break;
3199                   case '1':
3200                     if (endtoken[1] == '/')
3201                       endtype = GameIsDrawn;
3202                     else
3203                       endtype = WhiteWins;
3204                     break;
3205                 }
3206                 GameEnds(endtype, why, GE_ICS);
3207 #if ZIPPY
3208                 if (appData.zippyPlay && first.initDone) {
3209                     ZippyGameEnd(endtype, why);
3210                     if (first.pr == NULL) {
3211                       /* Start the next process early so that we'll
3212                          be ready for the next challenge */
3213                       StartChessProgram(&first);
3214                     }
3215                     /* Send "new" early, in case this command takes
3216                        a long time to finish, so that we'll be ready
3217                        for the next challenge. */
3218                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3219                     Reset(TRUE, TRUE);
3220                 }
3221 #endif /*ZIPPY*/
3222                 continue;
3223             }
3224
3225             if (looking_at(buf, &i, "Removing game * from observation") ||
3226                 looking_at(buf, &i, "no longer observing game *") ||
3227                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3228                 if (gameMode == IcsObserving &&
3229                     atoi(star_match[0]) == ics_gamenum)
3230                   {
3231                       /* icsEngineAnalyze */
3232                       if (appData.icsEngineAnalyze) {
3233                             ExitAnalyzeMode();
3234                             ModeHighlight();
3235                       }
3236                       StopClocks();
3237                       gameMode = IcsIdle;
3238                       ics_gamenum = -1;
3239                       ics_user_moved = FALSE;
3240                   }
3241                 continue;
3242             }
3243
3244             if (looking_at(buf, &i, "no longer examining game *")) {
3245                 if (gameMode == IcsExamining &&
3246                     atoi(star_match[0]) == ics_gamenum)
3247                   {
3248                       gameMode = IcsIdle;
3249                       ics_gamenum = -1;
3250                       ics_user_moved = FALSE;
3251                   }
3252                 continue;
3253             }
3254
3255             /* Advance leftover_start past any newlines we find,
3256                so only partial lines can get reparsed */
3257             if (looking_at(buf, &i, "\n")) {
3258                 prevColor = curColor;
3259                 if (curColor != ColorNormal) {
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = oldi;
3263                     }
3264                     Colorize(ColorNormal, FALSE);
3265                     curColor = ColorNormal;
3266                 }
3267                 if (started == STARTED_BOARD) {
3268                     started = STARTED_NONE;
3269                     parse[parse_pos] = NULLCHAR;
3270                     ParseBoard12(parse);
3271                     ics_user_moved = 0;
3272
3273                     /* Send premove here */
3274                     if (appData.premove) {
3275                       char str[MSG_SIZ];
3276                       if (currentMove == 0 &&
3277                           gameMode == IcsPlayingWhite &&
3278                           appData.premoveWhite) {
3279                         sprintf(str, "%s\n", appData.premoveWhiteText);
3280                         if (appData.debugMode)
3281                           fprintf(debugFP, "Sending premove:\n");
3282                         SendToICS(str);
3283                       } else if (currentMove == 1 &&
3284                                  gameMode == IcsPlayingBlack &&
3285                                  appData.premoveBlack) {
3286                         sprintf(str, "%s\n", appData.premoveBlackText);
3287                         if (appData.debugMode)
3288                           fprintf(debugFP, "Sending premove:\n");
3289                         SendToICS(str);
3290                       } else if (gotPremove) {
3291                         gotPremove = 0;
3292                         ClearPremoveHighlights();
3293                         if (appData.debugMode)
3294                           fprintf(debugFP, "Sending premove:\n");
3295                           UserMoveEvent(premoveFromX, premoveFromY, 
3296                                         premoveToX, premoveToY, 
3297                                         premovePromoChar);
3298                       }
3299                     }
3300
3301                     /* Usually suppress following prompt */
3302                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3303                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3304                         if (looking_at(buf, &i, "*% ")) {
3305                             savingComment = FALSE;
3306                             suppressKibitz = 0;
3307                         }
3308                     }
3309                     next_out = i;
3310                 } else if (started == STARTED_HOLDINGS) {
3311                     int gamenum;
3312                     char new_piece[MSG_SIZ];
3313                     started = STARTED_NONE;
3314                     parse[parse_pos] = NULLCHAR;
3315                     if (appData.debugMode)
3316                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3317                                                         parse, currentMove);
3318                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3319                         gamenum == ics_gamenum) {
3320                         if (gameInfo.variant == VariantNormal) {
3321                           /* [HGM] We seem to switch variant during a game!
3322                            * Presumably no holdings were displayed, so we have
3323                            * to move the position two files to the right to
3324                            * create room for them!
3325                            */
3326                           VariantClass newVariant;
3327                           switch(gameInfo.boardWidth) { // base guess on board width
3328                                 case 9:  newVariant = VariantShogi; break;
3329                                 case 10: newVariant = VariantGreat; break;
3330                                 default: newVariant = VariantCrazyhouse; break;
3331                           }
3332                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3333                           /* Get a move list just to see the header, which
3334                              will tell us whether this is really bug or zh */
3335                           if (ics_getting_history == H_FALSE) {
3336                             ics_getting_history = H_REQUESTED;
3337                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3338                             SendToICS(str);
3339                           }
3340                         }
3341                         new_piece[0] = NULLCHAR;
3342                         sscanf(parse, "game %d white [%s black [%s <- %s",
3343                                &gamenum, white_holding, black_holding,
3344                                new_piece);
3345                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3346                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3347                         /* [HGM] copy holdings to board holdings area */
3348                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3349                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3350                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3351 #if ZIPPY
3352                         if (appData.zippyPlay && first.initDone) {
3353                             ZippyHoldings(white_holding, black_holding,
3354                                           new_piece);
3355                         }
3356 #endif /*ZIPPY*/
3357                         if (tinyLayout || smallLayout) {
3358                             char wh[16], bh[16];
3359                             PackHolding(wh, white_holding);
3360                             PackHolding(bh, black_holding);
3361                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3362                                     gameInfo.white, gameInfo.black);
3363                         } else {
3364                             sprintf(str, "%s [%s] vs. %s [%s]",
3365                                     gameInfo.white, white_holding,
3366                                     gameInfo.black, black_holding);
3367                         }
3368
3369                         DrawPosition(FALSE, boards[currentMove]);
3370                         DisplayTitle(str);
3371                     }
3372                     /* Suppress following prompt */
3373                     if (looking_at(buf, &i, "*% ")) {
3374                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3375                         savingComment = FALSE;
3376                         suppressKibitz = 0;
3377                     }
3378                     next_out = i;
3379                 }
3380                 continue;
3381             }
3382
3383             i++;                /* skip unparsed character and loop back */
3384         }
3385         
3386         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3387 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3388 //          SendToPlayer(&buf[next_out], i - next_out);
3389             started != STARTED_HOLDINGS && leftover_start > next_out) {
3390             SendToPlayer(&buf[next_out], leftover_start - next_out);
3391             next_out = i;
3392         }
3393         
3394         leftover_len = buf_len - leftover_start;
3395         /* if buffer ends with something we couldn't parse,
3396            reparse it after appending the next read */
3397         
3398     } else if (count == 0) {
3399         RemoveInputSource(isr);
3400         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3401     } else {
3402         DisplayFatalError(_("Error reading from ICS"), error, 1);
3403     }
3404 }
3405
3406
3407 /* Board style 12 looks like this:
3408    
3409    <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
3410    
3411  * The "<12> " is stripped before it gets to this routine.  The two
3412  * trailing 0's (flip state and clock ticking) are later addition, and
3413  * some chess servers may not have them, or may have only the first.
3414  * Additional trailing fields may be added in the future.  
3415  */
3416
3417 #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"
3418
3419 #define RELATION_OBSERVING_PLAYED    0
3420 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3421 #define RELATION_PLAYING_MYMOVE      1
3422 #define RELATION_PLAYING_NOTMYMOVE  -1
3423 #define RELATION_EXAMINING           2
3424 #define RELATION_ISOLATED_BOARD     -3
3425 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3426
3427 void
3428 ParseBoard12(string)
3429      char *string;
3430
3431     GameMode newGameMode;
3432     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3433     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3434     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3435     char to_play, board_chars[200];
3436     char move_str[500], str[500], elapsed_time[500];
3437     char black[32], white[32];
3438     Board board;
3439     int prevMove = currentMove;
3440     int ticking = 2;
3441     ChessMove moveType;
3442     int fromX, fromY, toX, toY;
3443     char promoChar;
3444     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3445     char *bookHit = NULL; // [HGM] book
3446     Boolean weird = FALSE, reqFlag = FALSE;
3447
3448     fromX = fromY = toX = toY = -1;
3449     
3450     newGame = FALSE;
3451
3452     if (appData.debugMode)
3453       fprintf(debugFP, _("Parsing board: %s\n"), string);
3454
3455     move_str[0] = NULLCHAR;
3456     elapsed_time[0] = NULLCHAR;
3457     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3458         int  i = 0, j;
3459         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3460             if(string[i] == ' ') { ranks++; files = 0; }
3461             else files++;
3462             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3463             i++;
3464         }
3465         for(j = 0; j <i; j++) board_chars[j] = string[j];
3466         board_chars[i] = '\0';
3467         string += i + 1;
3468     }
3469     n = sscanf(string, PATTERN, &to_play, &double_push,
3470                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3471                &gamenum, white, black, &relation, &basetime, &increment,
3472                &white_stren, &black_stren, &white_time, &black_time,
3473                &moveNum, str, elapsed_time, move_str, &ics_flip,
3474                &ticking);
3475
3476     if (n < 21) {
3477         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3478         DisplayError(str, 0);
3479         return;
3480     }
3481
3482     /* Convert the move number to internal form */
3483     moveNum = (moveNum - 1) * 2;
3484     if (to_play == 'B') moveNum++;
3485     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3486       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3487                         0, 1);
3488       return;
3489     }
3490     
3491     switch (relation) {
3492       case RELATION_OBSERVING_PLAYED:
3493       case RELATION_OBSERVING_STATIC:
3494         if (gamenum == -1) {
3495             /* Old ICC buglet */
3496             relation = RELATION_OBSERVING_STATIC;
3497         }
3498         newGameMode = IcsObserving;
3499         break;
3500       case RELATION_PLAYING_MYMOVE:
3501       case RELATION_PLAYING_NOTMYMOVE:
3502         newGameMode =
3503           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3504             IcsPlayingWhite : IcsPlayingBlack;
3505         break;
3506       case RELATION_EXAMINING:
3507         newGameMode = IcsExamining;
3508         break;
3509       case RELATION_ISOLATED_BOARD:
3510       default:
3511         /* Just display this board.  If user was doing something else,
3512            we will forget about it until the next board comes. */ 
3513         newGameMode = IcsIdle;
3514         break;
3515       case RELATION_STARTING_POSITION:
3516         newGameMode = gameMode;
3517         break;
3518     }
3519     
3520     /* Modify behavior for initial board display on move listing
3521        of wild games.
3522        */
3523     switch (ics_getting_history) {
3524       case H_FALSE:
3525       case H_REQUESTED:
3526         break;
3527       case H_GOT_REQ_HEADER:
3528       case H_GOT_UNREQ_HEADER:
3529         /* This is the initial position of the current game */
3530         gamenum = ics_gamenum;
3531         moveNum = 0;            /* old ICS bug workaround */
3532         if (to_play == 'B') {
3533           startedFromSetupPosition = TRUE;
3534           blackPlaysFirst = TRUE;
3535           moveNum = 1;
3536           if (forwardMostMove == 0) forwardMostMove = 1;
3537           if (backwardMostMove == 0) backwardMostMove = 1;
3538           if (currentMove == 0) currentMove = 1;
3539         }
3540         newGameMode = gameMode;
3541         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3542         break;
3543       case H_GOT_UNWANTED_HEADER:
3544         /* This is an initial board that we don't want */
3545         return;
3546       case H_GETTING_MOVES:
3547         /* Should not happen */
3548         DisplayError(_("Error gathering move list: extra board"), 0);
3549         ics_getting_history = H_FALSE;
3550         return;
3551     }
3552
3553    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3554                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3555      /* [HGM] We seem to have switched variant unexpectedly
3556       * Try to guess new variant from board size
3557       */
3558           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3559           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3560           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3561           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3562           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3563           if(!weird) newVariant = VariantNormal;
3564           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3565           /* Get a move list just to see the header, which
3566              will tell us whether this is really bug or zh */
3567           if (ics_getting_history == H_FALSE) {
3568             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3569             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3570             SendToICS(str);
3571           }
3572     }
3573     
3574     /* Take action if this is the first board of a new game, or of a
3575        different game than is currently being displayed.  */
3576     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3577         relation == RELATION_ISOLATED_BOARD) {
3578         
3579         /* Forget the old game and get the history (if any) of the new one */
3580         if (gameMode != BeginningOfGame) {
3581           Reset(TRUE, TRUE);
3582         }
3583         newGame = TRUE;
3584         if (appData.autoRaiseBoard) BoardToTop();
3585         prevMove = -3;
3586         if (gamenum == -1) {
3587             newGameMode = IcsIdle;
3588         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3589                    appData.getMoveList && !reqFlag) {
3590             /* Need to get game history */
3591             ics_getting_history = H_REQUESTED;
3592             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3593             SendToICS(str);
3594         }
3595         
3596         /* Initially flip the board to have black on the bottom if playing
3597            black or if the ICS flip flag is set, but let the user change
3598            it with the Flip View button. */
3599         flipView = appData.autoFlipView ? 
3600           (newGameMode == IcsPlayingBlack) || ics_flip :
3601           appData.flipView;
3602         
3603         /* Done with values from previous mode; copy in new ones */
3604         gameMode = newGameMode;
3605         ModeHighlight();
3606         ics_gamenum = gamenum;
3607         if (gamenum == gs_gamenum) {
3608             int klen = strlen(gs_kind);
3609             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3610             sprintf(str, "ICS %s", gs_kind);
3611             gameInfo.event = StrSave(str);
3612         } else {
3613             gameInfo.event = StrSave("ICS game");
3614         }
3615         gameInfo.site = StrSave(appData.icsHost);
3616         gameInfo.date = PGNDate();
3617         gameInfo.round = StrSave("-");
3618         gameInfo.white = StrSave(white);
3619         gameInfo.black = StrSave(black);
3620         timeControl = basetime * 60 * 1000;
3621         timeControl_2 = 0;
3622         timeIncrement = increment * 1000;
3623         movesPerSession = 0;
3624         gameInfo.timeControl = TimeControlTagValue();
3625         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3626   if (appData.debugMode) {
3627     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3628     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3629     setbuf(debugFP, NULL);
3630   }
3631
3632         gameInfo.outOfBook = NULL;
3633         
3634         /* Do we have the ratings? */
3635         if (strcmp(player1Name, white) == 0 &&
3636             strcmp(player2Name, black) == 0) {
3637             if (appData.debugMode)
3638               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3639                       player1Rating, player2Rating);
3640             gameInfo.whiteRating = player1Rating;
3641             gameInfo.blackRating = player2Rating;
3642         } else if (strcmp(player2Name, white) == 0 &&
3643                    strcmp(player1Name, black) == 0) {
3644             if (appData.debugMode)
3645               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3646                       player2Rating, player1Rating);
3647             gameInfo.whiteRating = player2Rating;
3648             gameInfo.blackRating = player1Rating;
3649         }
3650         player1Name[0] = player2Name[0] = NULLCHAR;
3651
3652         /* Silence shouts if requested */
3653         if (appData.quietPlay &&
3654             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3655             SendToICS(ics_prefix);
3656             SendToICS("set shout 0\n");
3657         }
3658     }
3659     
3660     /* Deal with midgame name changes */
3661     if (!newGame) {
3662         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3663             if (gameInfo.white) free(gameInfo.white);
3664             gameInfo.white = StrSave(white);
3665         }
3666         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3667             if (gameInfo.black) free(gameInfo.black);
3668             gameInfo.black = StrSave(black);
3669         }
3670     }
3671     
3672     /* Throw away game result if anything actually changes in examine mode */
3673     if (gameMode == IcsExamining && !newGame) {
3674         gameInfo.result = GameUnfinished;
3675         if (gameInfo.resultDetails != NULL) {
3676             free(gameInfo.resultDetails);
3677             gameInfo.resultDetails = NULL;
3678         }
3679     }
3680     
3681     /* In pausing && IcsExamining mode, we ignore boards coming
3682        in if they are in a different variation than we are. */
3683     if (pauseExamInvalid) return;
3684     if (pausing && gameMode == IcsExamining) {
3685         if (moveNum <= pauseExamForwardMostMove) {
3686             pauseExamInvalid = TRUE;
3687             forwardMostMove = pauseExamForwardMostMove;
3688             return;
3689         }
3690     }
3691     
3692   if (appData.debugMode) {
3693     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3694   }
3695     /* Parse the board */
3696     for (k = 0; k < ranks; k++) {
3697       for (j = 0; j < files; j++)
3698         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3699       if(gameInfo.holdingsWidth > 1) {
3700            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3701            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3702       }
3703     }
3704     CopyBoard(boards[moveNum], board);
3705     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3706     if (moveNum == 0) {
3707         startedFromSetupPosition =
3708           !CompareBoards(board, initialPosition);
3709         if(startedFromSetupPosition)
3710             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3711     }
3712
3713     /* [HGM] Set castling rights. Take the outermost Rooks,
3714        to make it also work for FRC opening positions. Note that board12
3715        is really defective for later FRC positions, as it has no way to
3716        indicate which Rook can castle if they are on the same side of King.
3717        For the initial position we grant rights to the outermost Rooks,
3718        and remember thos rights, and we then copy them on positions
3719        later in an FRC game. This means WB might not recognize castlings with
3720        Rooks that have moved back to their original position as illegal,
3721        but in ICS mode that is not its job anyway.
3722     */
3723     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3724     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3725
3726         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3727             if(board[0][i] == WhiteRook) j = i;
3728         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3729         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3730             if(board[0][i] == WhiteRook) j = i;
3731         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3732         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3733             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3734         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3735         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3736             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3737         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3738
3739         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3740         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3741             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3742         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743             if(board[BOARD_HEIGHT-1][k] == bKing)
3744                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3745         if(gameInfo.variant == VariantTwoKings) {
3746             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3747             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3748             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3749         }
3750     } else { int r;
3751         r = boards[moveNum][CASTLING][0] = initialRights[0];
3752         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3753         r = boards[moveNum][CASTLING][1] = initialRights[1];
3754         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3755         r = boards[moveNum][CASTLING][3] = initialRights[3];
3756         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3757         r = boards[moveNum][CASTLING][4] = initialRights[4];
3758         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3759         /* wildcastle kludge: always assume King has rights */
3760         r = boards[moveNum][CASTLING][2] = initialRights[2];
3761         r = boards[moveNum][CASTLING][5] = initialRights[5];
3762     }
3763     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3764     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3765
3766     
3767     if (ics_getting_history == H_GOT_REQ_HEADER ||
3768         ics_getting_history == H_GOT_UNREQ_HEADER) {
3769         /* This was an initial position from a move list, not
3770            the current position */
3771         return;
3772     }
3773     
3774     /* Update currentMove and known move number limits */
3775     newMove = newGame || moveNum > forwardMostMove;
3776
3777     if (newGame) {
3778         forwardMostMove = backwardMostMove = currentMove = moveNum;
3779         if (gameMode == IcsExamining && moveNum == 0) {
3780           /* Workaround for ICS limitation: we are not told the wild
3781              type when starting to examine a game.  But if we ask for
3782              the move list, the move list header will tell us */
3783             ics_getting_history = H_REQUESTED;
3784             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3785             SendToICS(str);
3786         }
3787     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3788                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3789 #if ZIPPY
3790         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3791         /* [HGM] applied this also to an engine that is silently watching        */
3792         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3793             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3794             gameInfo.variant == currentlyInitializedVariant) {
3795           takeback = forwardMostMove - moveNum;
3796           for (i = 0; i < takeback; i++) {
3797             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3798             SendToProgram("undo\n", &first);
3799           }
3800         }
3801 #endif
3802
3803         forwardMostMove = moveNum;
3804         if (!pausing || currentMove > forwardMostMove)
3805           currentMove = forwardMostMove;
3806     } else {
3807         /* New part of history that is not contiguous with old part */ 
3808         if (pausing && gameMode == IcsExamining) {
3809             pauseExamInvalid = TRUE;
3810             forwardMostMove = pauseExamForwardMostMove;
3811             return;
3812         }
3813         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3814 #if ZIPPY
3815             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3816                 // [HGM] when we will receive the move list we now request, it will be
3817                 // fed to the engine from the first move on. So if the engine is not
3818                 // in the initial position now, bring it there.
3819                 InitChessProgram(&first, 0);
3820             }
3821 #endif
3822             ics_getting_history = H_REQUESTED;
3823             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3824             SendToICS(str);
3825         }
3826         forwardMostMove = backwardMostMove = currentMove = moveNum;
3827     }
3828     
3829     /* Update the clocks */
3830     if (strchr(elapsed_time, '.')) {
3831       /* Time is in ms */
3832       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3833       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3834     } else {
3835       /* Time is in seconds */
3836       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3837       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3838     }
3839       
3840
3841 #if ZIPPY
3842     if (appData.zippyPlay && newGame &&
3843         gameMode != IcsObserving && gameMode != IcsIdle &&
3844         gameMode != IcsExamining)
3845       ZippyFirstBoard(moveNum, basetime, increment);
3846 #endif
3847     
3848     /* Put the move on the move list, first converting
3849        to canonical algebraic form. */
3850     if (moveNum > 0) {
3851   if (appData.debugMode) {
3852     if (appData.debugMode) { int f = forwardMostMove;
3853         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3854                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3855                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3856     }
3857     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3858     fprintf(debugFP, "moveNum = %d\n", moveNum);
3859     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3860     setbuf(debugFP, NULL);
3861   }
3862         if (moveNum <= backwardMostMove) {
3863             /* We don't know what the board looked like before
3864                this move.  Punt. */
3865             strcpy(parseList[moveNum - 1], move_str);
3866             strcat(parseList[moveNum - 1], " ");
3867             strcat(parseList[moveNum - 1], elapsed_time);
3868             moveList[moveNum - 1][0] = NULLCHAR;
3869         } else if (strcmp(move_str, "none") == 0) {
3870             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3871             /* Again, we don't know what the board looked like;
3872                this is really the start of the game. */
3873             parseList[moveNum - 1][0] = NULLCHAR;
3874             moveList[moveNum - 1][0] = NULLCHAR;
3875             backwardMostMove = moveNum;
3876             startedFromSetupPosition = TRUE;
3877             fromX = fromY = toX = toY = -1;
3878         } else {
3879           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3880           //                 So we parse the long-algebraic move string in stead of the SAN move
3881           int valid; char buf[MSG_SIZ], *prom;
3882
3883           // str looks something like "Q/a1-a2"; kill the slash
3884           if(str[1] == '/') 
3885                 sprintf(buf, "%c%s", str[0], str+2);
3886           else  strcpy(buf, str); // might be castling
3887           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3888                 strcat(buf, prom); // long move lacks promo specification!
3889           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3890                 if(appData.debugMode) 
3891                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3892                 strcpy(move_str, buf);
3893           }
3894           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3895                                 &fromX, &fromY, &toX, &toY, &promoChar)
3896                || ParseOneMove(buf, moveNum - 1, &moveType,
3897                                 &fromX, &fromY, &toX, &toY, &promoChar);
3898           // end of long SAN patch
3899           if (valid) {
3900             (void) CoordsToAlgebraic(boards[moveNum - 1],
3901                                      PosFlags(moveNum - 1),
3902                                      fromY, fromX, toY, toX, promoChar,
3903                                      parseList[moveNum-1]);
3904             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3905               case MT_NONE:
3906               case MT_STALEMATE:
3907               default:
3908                 break;
3909               case MT_CHECK:
3910                 if(gameInfo.variant != VariantShogi)
3911                     strcat(parseList[moveNum - 1], "+");
3912                 break;
3913               case MT_CHECKMATE:
3914               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3915                 strcat(parseList[moveNum - 1], "#");
3916                 break;
3917             }
3918             strcat(parseList[moveNum - 1], " ");
3919             strcat(parseList[moveNum - 1], elapsed_time);
3920             /* currentMoveString is set as a side-effect of ParseOneMove */
3921             strcpy(moveList[moveNum - 1], currentMoveString);
3922             strcat(moveList[moveNum - 1], "\n");
3923           } else {
3924             /* Move from ICS was illegal!?  Punt. */
3925   if (appData.debugMode) {
3926     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3927     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3928   }
3929             strcpy(parseList[moveNum - 1], move_str);
3930             strcat(parseList[moveNum - 1], " ");
3931             strcat(parseList[moveNum - 1], elapsed_time);
3932             moveList[moveNum - 1][0] = NULLCHAR;
3933             fromX = fromY = toX = toY = -1;
3934           }
3935         }
3936   if (appData.debugMode) {
3937     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3938     setbuf(debugFP, NULL);
3939   }
3940
3941 #if ZIPPY
3942         /* Send move to chess program (BEFORE animating it). */
3943         if (appData.zippyPlay && !newGame && newMove && 
3944            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3945
3946             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3947                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3948                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3949                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3950                             move_str);
3951                     DisplayError(str, 0);
3952                 } else {
3953                     if (first.sendTime) {
3954                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3955                     }
3956                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3957                     if (firstMove && !bookHit) {
3958                         firstMove = FALSE;
3959                         if (first.useColors) {
3960                           SendToProgram(gameMode == IcsPlayingWhite ?
3961                                         "white\ngo\n" :
3962                                         "black\ngo\n", &first);
3963                         } else {
3964                           SendToProgram("go\n", &first);
3965                         }
3966                         first.maybeThinking = TRUE;
3967                     }
3968                 }
3969             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3970               if (moveList[moveNum - 1][0] == NULLCHAR) {
3971                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3972                 DisplayError(str, 0);
3973               } else {
3974                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3975                 SendMoveToProgram(moveNum - 1, &first);
3976               }
3977             }
3978         }
3979 #endif
3980     }
3981
3982     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3983         /* If move comes from a remote source, animate it.  If it
3984            isn't remote, it will have already been animated. */
3985         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3986             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3987         }
3988         if (!pausing && appData.highlightLastMove) {
3989             SetHighlights(fromX, fromY, toX, toY);
3990         }
3991     }
3992     
3993     /* Start the clocks */
3994     whiteFlag = blackFlag = FALSE;
3995     appData.clockMode = !(basetime == 0 && increment == 0);
3996     if (ticking == 0) {
3997       ics_clock_paused = TRUE;
3998       StopClocks();
3999     } else if (ticking == 1) {
4000       ics_clock_paused = FALSE;
4001     }
4002     if (gameMode == IcsIdle ||
4003         relation == RELATION_OBSERVING_STATIC ||
4004         relation == RELATION_EXAMINING ||
4005         ics_clock_paused)
4006       DisplayBothClocks();
4007     else
4008       StartClocks();
4009     
4010     /* Display opponents and material strengths */
4011     if (gameInfo.variant != VariantBughouse &&
4012         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4013         if (tinyLayout || smallLayout) {
4014             if(gameInfo.variant == VariantNormal)
4015                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4016                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4017                     basetime, increment);
4018             else
4019                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4020                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4021                     basetime, increment, (int) gameInfo.variant);
4022         } else {
4023             if(gameInfo.variant == VariantNormal)
4024                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4025                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4026                     basetime, increment);
4027             else
4028                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4029                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4030                     basetime, increment, VariantName(gameInfo.variant));
4031         }
4032         DisplayTitle(str);
4033   if (appData.debugMode) {
4034     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4035   }
4036     }
4037
4038    
4039     /* Display the board */
4040     if (!pausing && !appData.noGUI) {
4041       
4042       if (appData.premove)
4043           if (!gotPremove || 
4044              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4045              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4046               ClearPremoveHighlights();
4047
4048       DrawPosition(FALSE, boards[currentMove]);
4049       DisplayMove(moveNum - 1);
4050       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4051             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4052               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4053         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4054       }
4055     }
4056
4057     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4058 #if ZIPPY
4059     if(bookHit) { // [HGM] book: simulate book reply
4060         static char bookMove[MSG_SIZ]; // a bit generous?
4061
4062         programStats.nodes = programStats.depth = programStats.time = 
4063         programStats.score = programStats.got_only_move = 0;
4064         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4065
4066         strcpy(bookMove, "move ");
4067         strcat(bookMove, bookHit);
4068         HandleMachineMove(bookMove, &first);
4069     }
4070 #endif
4071 }
4072
4073 void
4074 GetMoveListEvent()
4075 {
4076     char buf[MSG_SIZ];
4077     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4078         ics_getting_history = H_REQUESTED;
4079         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4080         SendToICS(buf);
4081     }
4082 }
4083
4084 void
4085 AnalysisPeriodicEvent(force)
4086      int force;
4087 {
4088     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4089          && !force) || !appData.periodicUpdates)
4090       return;
4091
4092     /* Send . command to Crafty to collect stats */
4093     SendToProgram(".\n", &first);
4094
4095     /* Don't send another until we get a response (this makes
4096        us stop sending to old Crafty's which don't understand
4097        the "." command (sending illegal cmds resets node count & time,
4098        which looks bad)) */
4099     programStats.ok_to_send = 0;
4100 }
4101
4102 void ics_update_width(new_width)
4103         int new_width;
4104 {
4105         ics_printf("set width %d\n", new_width);
4106 }
4107
4108 void
4109 SendMoveToProgram(moveNum, cps)
4110      int moveNum;
4111      ChessProgramState *cps;
4112 {
4113     char buf[MSG_SIZ];
4114
4115     if (cps->useUsermove) {
4116       SendToProgram("usermove ", cps);
4117     }
4118     if (cps->useSAN) {
4119       char *space;
4120       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4121         int len = space - parseList[moveNum];
4122         memcpy(buf, parseList[moveNum], len);
4123         buf[len++] = '\n';
4124         buf[len] = NULLCHAR;
4125       } else {
4126         sprintf(buf, "%s\n", parseList[moveNum]);
4127       }
4128       SendToProgram(buf, cps);
4129     } else {
4130       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4131         AlphaRank(moveList[moveNum], 4);
4132         SendToProgram(moveList[moveNum], cps);
4133         AlphaRank(moveList[moveNum], 4); // and back
4134       } else
4135       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4136        * the engine. It would be nice to have a better way to identify castle 
4137        * moves here. */
4138       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4139                                                                          && cps->useOOCastle) {
4140         int fromX = moveList[moveNum][0] - AAA; 
4141         int fromY = moveList[moveNum][1] - ONE;
4142         int toX = moveList[moveNum][2] - AAA; 
4143         int toY = moveList[moveNum][3] - ONE;
4144         if((boards[moveNum][fromY][fromX] == WhiteKing 
4145             && boards[moveNum][toY][toX] == WhiteRook)
4146            || (boards[moveNum][fromY][fromX] == BlackKing 
4147                && boards[moveNum][toY][toX] == BlackRook)) {
4148           if(toX > fromX) SendToProgram("O-O\n", cps);
4149           else SendToProgram("O-O-O\n", cps);
4150         }
4151         else SendToProgram(moveList[moveNum], cps);
4152       }
4153       else SendToProgram(moveList[moveNum], cps);
4154       /* End of additions by Tord */
4155     }
4156
4157     /* [HGM] setting up the opening has brought engine in force mode! */
4158     /*       Send 'go' if we are in a mode where machine should play. */
4159     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4160         (gameMode == TwoMachinesPlay   ||
4161 #ifdef ZIPPY
4162          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4163 #endif
4164          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4165         SendToProgram("go\n", cps);
4166   if (appData.debugMode) {
4167     fprintf(debugFP, "(extra)\n");
4168   }
4169     }
4170     setboardSpoiledMachineBlack = 0;
4171 }
4172
4173 void
4174 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4175      ChessMove moveType;
4176      int fromX, fromY, toX, toY;
4177 {
4178     char user_move[MSG_SIZ];
4179
4180     switch (moveType) {
4181       default:
4182         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4183                 (int)moveType, fromX, fromY, toX, toY);
4184         DisplayError(user_move + strlen("say "), 0);
4185         break;
4186       case WhiteKingSideCastle:
4187       case BlackKingSideCastle:
4188       case WhiteQueenSideCastleWild:
4189       case BlackQueenSideCastleWild:
4190       /* PUSH Fabien */
4191       case WhiteHSideCastleFR:
4192       case BlackHSideCastleFR:
4193       /* POP Fabien */
4194         sprintf(user_move, "o-o\n");
4195         break;
4196       case WhiteQueenSideCastle:
4197       case BlackQueenSideCastle:
4198       case WhiteKingSideCastleWild:
4199       case BlackKingSideCastleWild:
4200       /* PUSH Fabien */
4201       case WhiteASideCastleFR:
4202       case BlackASideCastleFR:
4203       /* POP Fabien */
4204         sprintf(user_move, "o-o-o\n");
4205         break;
4206       case WhitePromotionQueen:
4207       case BlackPromotionQueen:
4208       case WhitePromotionRook:
4209       case BlackPromotionRook:
4210       case WhitePromotionBishop:
4211       case BlackPromotionBishop:
4212       case WhitePromotionKnight:
4213       case BlackPromotionKnight:
4214       case WhitePromotionKing:
4215       case BlackPromotionKing:
4216       case WhitePromotionChancellor:
4217       case BlackPromotionChancellor:
4218       case WhitePromotionArchbishop:
4219       case BlackPromotionArchbishop:
4220         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4221             sprintf(user_move, "%c%c%c%c=%c\n",
4222                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4223                 PieceToChar(WhiteFerz));
4224         else if(gameInfo.variant == VariantGreat)
4225             sprintf(user_move, "%c%c%c%c=%c\n",
4226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4227                 PieceToChar(WhiteMan));
4228         else
4229             sprintf(user_move, "%c%c%c%c=%c\n",
4230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4231                 PieceToChar(PromoPiece(moveType)));
4232         break;
4233       case WhiteDrop:
4234       case BlackDrop:
4235         sprintf(user_move, "%c@%c%c\n",
4236                 ToUpper(PieceToChar((ChessSquare) fromX)),
4237                 AAA + toX, ONE + toY);
4238         break;
4239       case NormalMove:
4240       case WhiteCapturesEnPassant:
4241       case BlackCapturesEnPassant:
4242       case IllegalMove:  /* could be a variant we don't quite understand */
4243         sprintf(user_move, "%c%c%c%c\n",
4244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4245         break;
4246     }
4247     SendToICS(user_move);
4248     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4249         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4250 }
4251
4252 void
4253 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4254      int rf, ff, rt, ft;
4255      char promoChar;
4256      char move[7];
4257 {
4258     if (rf == DROP_RANK) {
4259         sprintf(move, "%c@%c%c\n",
4260                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4261     } else {
4262         if (promoChar == 'x' || promoChar == NULLCHAR) {
4263             sprintf(move, "%c%c%c%c\n",
4264                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4265         } else {
4266             sprintf(move, "%c%c%c%c%c\n",
4267                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4268         }
4269     }
4270 }
4271
4272 void
4273 ProcessICSInitScript(f)
4274      FILE *f;
4275 {
4276     char buf[MSG_SIZ];
4277
4278     while (fgets(buf, MSG_SIZ, f)) {
4279         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4280     }
4281
4282     fclose(f);
4283 }
4284
4285
4286 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4287 void
4288 AlphaRank(char *move, int n)
4289 {
4290 //    char *p = move, c; int x, y;
4291
4292     if (appData.debugMode) {
4293         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4294     }
4295
4296     if(move[1]=='*' && 
4297        move[2]>='0' && move[2]<='9' &&
4298        move[3]>='a' && move[3]<='x'    ) {
4299         move[1] = '@';
4300         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4301         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4302     } else
4303     if(move[0]>='0' && move[0]<='9' &&
4304        move[1]>='a' && move[1]<='x' &&
4305        move[2]>='0' && move[2]<='9' &&
4306        move[3]>='a' && move[3]<='x'    ) {
4307         /* input move, Shogi -> normal */
4308         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4309         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4310         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4311         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4312     } else
4313     if(move[1]=='@' &&
4314        move[3]>='0' && move[3]<='9' &&
4315        move[2]>='a' && move[2]<='x'    ) {
4316         move[1] = '*';
4317         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4318         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4319     } else
4320     if(
4321        move[0]>='a' && move[0]<='x' &&
4322        move[3]>='0' && move[3]<='9' &&
4323        move[2]>='a' && move[2]<='x'    ) {
4324          /* output move, normal -> Shogi */
4325         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4326         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4327         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4328         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4329         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4330     }
4331     if (appData.debugMode) {
4332         fprintf(debugFP, "   out = '%s'\n", move);
4333     }
4334 }
4335
4336 /* Parser for moves from gnuchess, ICS, or user typein box */
4337 Boolean
4338 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4339      char *move;
4340      int moveNum;
4341      ChessMove *moveType;
4342      int *fromX, *fromY, *toX, *toY;
4343      char *promoChar;
4344 {       
4345     if (appData.debugMode) {
4346         fprintf(debugFP, "move to parse: %s\n", move);
4347     }
4348     *moveType = yylexstr(moveNum, move);
4349
4350     switch (*moveType) {
4351       case WhitePromotionChancellor:
4352       case BlackPromotionChancellor:
4353       case WhitePromotionArchbishop:
4354       case BlackPromotionArchbishop:
4355       case WhitePromotionQueen:
4356       case BlackPromotionQueen:
4357       case WhitePromotionRook:
4358       case BlackPromotionRook:
4359       case WhitePromotionBishop:
4360       case BlackPromotionBishop:
4361       case WhitePromotionKnight:
4362       case BlackPromotionKnight:
4363       case WhitePromotionKing:
4364       case BlackPromotionKing:
4365       case NormalMove:
4366       case WhiteCapturesEnPassant:
4367       case BlackCapturesEnPassant:
4368       case WhiteKingSideCastle:
4369       case WhiteQueenSideCastle:
4370       case BlackKingSideCastle:
4371       case BlackQueenSideCastle:
4372       case WhiteKingSideCastleWild:
4373       case WhiteQueenSideCastleWild:
4374       case BlackKingSideCastleWild:
4375       case BlackQueenSideCastleWild:
4376       /* Code added by Tord: */
4377       case WhiteHSideCastleFR:
4378       case WhiteASideCastleFR:
4379       case BlackHSideCastleFR:
4380       case BlackASideCastleFR:
4381       /* End of code added by Tord */
4382       case IllegalMove:         /* bug or odd chess variant */
4383         *fromX = currentMoveString[0] - AAA;
4384         *fromY = currentMoveString[1] - ONE;
4385         *toX = currentMoveString[2] - AAA;
4386         *toY = currentMoveString[3] - ONE;
4387         *promoChar = currentMoveString[4];
4388         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4389             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4390     if (appData.debugMode) {
4391         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4392     }
4393             *fromX = *fromY = *toX = *toY = 0;
4394             return FALSE;
4395         }
4396         if (appData.testLegality) {
4397           return (*moveType != IllegalMove);
4398         } else {
4399           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4400                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4401         }
4402
4403       case WhiteDrop:
4404       case BlackDrop:
4405         *fromX = *moveType == WhiteDrop ?
4406           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4407           (int) CharToPiece(ToLower(currentMoveString[0]));
4408         *fromY = DROP_RANK;
4409         *toX = currentMoveString[2] - AAA;
4410         *toY = currentMoveString[3] - ONE;
4411         *promoChar = NULLCHAR;
4412         return TRUE;
4413
4414       case AmbiguousMove:
4415       case ImpossibleMove:
4416       case (ChessMove) 0:       /* end of file */
4417       case ElapsedTime:
4418       case Comment:
4419       case PGNTag:
4420       case NAG:
4421       case WhiteWins:
4422       case BlackWins:
4423       case GameIsDrawn:
4424       default:
4425     if (appData.debugMode) {
4426         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4427     }
4428         /* bug? */
4429         *fromX = *fromY = *toX = *toY = 0;
4430         *promoChar = NULLCHAR;
4431         return FALSE;
4432     }
4433 }
4434
4435
4436 void
4437 ParsePV(char *pv)
4438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4439   int fromX, fromY, toX, toY; char promoChar;
4440   ChessMove moveType;
4441   Boolean valid;
4442   int nr = 0;
4443
4444   endPV = forwardMostMove;
4445   do {
4446     while(*pv == ' ') pv++;
4447     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4448     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4449 if(appData.debugMode){
4450 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4451 }
4452     if(!valid && nr == 0 &&
4453        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4454         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4455     }
4456     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4457     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4458     nr++;
4459     if(endPV+1 > framePtr) break; // no space, truncate
4460     if(!valid) break;
4461     endPV++;
4462     CopyBoard(boards[endPV], boards[endPV-1]);
4463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4464     moveList[endPV-1][0] = fromX + AAA;
4465     moveList[endPV-1][1] = fromY + ONE;
4466     moveList[endPV-1][2] = toX + AAA;
4467     moveList[endPV-1][3] = toY + ONE;
4468     parseList[endPV-1][0] = NULLCHAR;
4469   } while(valid);
4470   currentMove = endPV;
4471   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4472   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4473                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4474   DrawPosition(TRUE, boards[currentMove]);
4475 }
4476
4477 static int lastX, lastY;
4478
4479 Boolean
4480 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4481 {
4482         int startPV;
4483
4484         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4485         lastX = x; lastY = y;
4486         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4487         startPV = index;
4488       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4489       index = startPV;
4490         while(buf[index] && buf[index] != '\n') index++;
4491         buf[index] = 0;
4492         ParsePV(buf+startPV);
4493         *start = startPV; *end = index-1;
4494         return TRUE;
4495 }
4496
4497 Boolean
4498 LoadPV(int x, int y)
4499 { // called on right mouse click to load PV
4500   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4501   lastX = x; lastY = y;
4502   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4503   return TRUE;
4504 }
4505
4506 void
4507 UnLoadPV()
4508 {
4509   if(endPV < 0) return;
4510   endPV = -1;
4511   currentMove = forwardMostMove;
4512   ClearPremoveHighlights();
4513   DrawPosition(TRUE, boards[currentMove]);
4514 }
4515
4516 void
4517 MovePV(int x, int y, int h)
4518 { // step through PV based on mouse coordinates (called on mouse move)
4519   int margin = h>>3, step = 0;
4520
4521   if(endPV < 0) return;
4522   // we must somehow check if right button is still down (might be released off board!)
4523   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4524   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4525   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4526   if(!step) return;
4527   lastX = x; lastY = y;
4528   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4529   currentMove += step;
4530   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4531   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4532                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4533   DrawPosition(FALSE, boards[currentMove]);
4534 }
4535
4536
4537 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4538 // All positions will have equal probability, but the current method will not provide a unique
4539 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4540 #define DARK 1
4541 #define LITE 2
4542 #define ANY 3
4543
4544 int squaresLeft[4];
4545 int piecesLeft[(int)BlackPawn];
4546 int seed, nrOfShuffles;
4547
4548 void GetPositionNumber()
4549 {       // sets global variable seed
4550         int i;
4551
4552         seed = appData.defaultFrcPosition;
4553         if(seed < 0) { // randomize based on time for negative FRC position numbers
4554                 for(i=0; i<50; i++) seed += random();
4555                 seed = random() ^ random() >> 8 ^ random() << 8;
4556                 if(seed<0) seed = -seed;
4557         }
4558 }
4559
4560 int put(Board board, int pieceType, int rank, int n, int shade)
4561 // put the piece on the (n-1)-th empty squares of the given shade
4562 {
4563         int i;
4564
4565         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4566                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4567                         board[rank][i] = (ChessSquare) pieceType;
4568                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4569                         squaresLeft[ANY]--;
4570                         piecesLeft[pieceType]--; 
4571                         return i;
4572                 }
4573         }
4574         return -1;
4575 }
4576
4577
4578 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4579 // calculate where the next piece goes, (any empty square), and put it there
4580 {
4581         int i;
4582
4583         i = seed % squaresLeft[shade];
4584         nrOfShuffles *= squaresLeft[shade];
4585         seed /= squaresLeft[shade];
4586         put(board, pieceType, rank, i, shade);
4587 }
4588
4589 void AddTwoPieces(Board board, int pieceType, int rank)
4590 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4591 {
4592         int i, n=squaresLeft[ANY], j=n-1, k;
4593
4594         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4595         i = seed % k;  // pick one
4596         nrOfShuffles *= k;
4597         seed /= k;
4598         while(i >= j) i -= j--;
4599         j = n - 1 - j; i += j;
4600         put(board, pieceType, rank, j, ANY);
4601         put(board, pieceType, rank, i, ANY);
4602 }
4603
4604 void SetUpShuffle(Board board, int number)
4605 {
4606         int i, p, first=1;
4607
4608         GetPositionNumber(); nrOfShuffles = 1;
4609
4610         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4611         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4612         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4613
4614         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4615
4616         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4617             p = (int) board[0][i];
4618             if(p < (int) BlackPawn) piecesLeft[p] ++;
4619             board[0][i] = EmptySquare;
4620         }
4621
4622         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4623             // shuffles restricted to allow normal castling put KRR first
4624             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4625                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4626             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4627                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4628             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4629                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4630             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4631                 put(board, WhiteRook, 0, 0, ANY);
4632             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4633         }
4634
4635         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4636             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4637             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4638                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4639                 while(piecesLeft[p] >= 2) {
4640                     AddOnePiece(board, p, 0, LITE);
4641                     AddOnePiece(board, p, 0, DARK);
4642                 }
4643                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4644             }
4645
4646         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4647             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4648             // but we leave King and Rooks for last, to possibly obey FRC restriction
4649             if(p == (int)WhiteRook) continue;
4650             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4651             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4652         }
4653
4654         // now everything is placed, except perhaps King (Unicorn) and Rooks
4655
4656         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4657             // Last King gets castling rights
4658             while(piecesLeft[(int)WhiteUnicorn]) {
4659                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4660                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4661             }
4662
4663             while(piecesLeft[(int)WhiteKing]) {
4664                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4665                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4666             }
4667
4668
4669         } else {
4670             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4671             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4672         }
4673
4674         // Only Rooks can be left; simply place them all
4675         while(piecesLeft[(int)WhiteRook]) {
4676                 i = put(board, WhiteRook, 0, 0, ANY);
4677                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4678                         if(first) {
4679                                 first=0;
4680                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4681                         }
4682                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4683                 }
4684         }
4685         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4686             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4687         }
4688
4689         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4690 }
4691
4692 int SetCharTable( char *table, const char * map )
4693 /* [HGM] moved here from winboard.c because of its general usefulness */
4694 /*       Basically a safe strcpy that uses the last character as King */
4695 {
4696     int result = FALSE; int NrPieces;
4697
4698     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4699                     && NrPieces >= 12 && !(NrPieces&1)) {
4700         int i; /* [HGM] Accept even length from 12 to 34 */
4701
4702         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4703         for( i=0; i<NrPieces/2-1; i++ ) {
4704             table[i] = map[i];
4705             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4706         }
4707         table[(int) WhiteKing]  = map[NrPieces/2-1];
4708         table[(int) BlackKing]  = map[NrPieces-1];
4709
4710         result = TRUE;
4711     }
4712
4713     return result;
4714 }
4715
4716 void Prelude(Board board)
4717 {       // [HGM] superchess: random selection of exo-pieces
4718         int i, j, k; ChessSquare p; 
4719         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4720
4721         GetPositionNumber(); // use FRC position number
4722
4723         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4724             SetCharTable(pieceToChar, appData.pieceToCharTable);
4725             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4726                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4727         }
4728
4729         j = seed%4;                 seed /= 4; 
4730         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4731         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4732         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4733         j = seed%3 + (seed%3 >= j); seed /= 3; 
4734         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4735         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4736         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4737         j = seed%3;                 seed /= 3; 
4738         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4739         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4740         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4741         j = seed%2 + (seed%2 >= j); seed /= 2; 
4742         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4743         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4744         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4745         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4746         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4747         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4748         put(board, exoPieces[0],    0, 0, ANY);
4749         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4750 }
4751
4752 void
4753 InitPosition(redraw)
4754      int redraw;
4755 {
4756     ChessSquare (* pieces)[BOARD_FILES];
4757     int i, j, pawnRow, overrule,
4758     oldx = gameInfo.boardWidth,
4759     oldy = gameInfo.boardHeight,
4760     oldh = gameInfo.holdingsWidth,
4761     oldv = gameInfo.variant;
4762
4763     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4764
4765     /* [AS] Initialize pv info list [HGM] and game status */
4766     {
4767         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4768             pvInfoList[i].depth = 0;
4769             boards[i][EP_STATUS] = EP_NONE;
4770             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4771         }
4772
4773         initialRulePlies = 0; /* 50-move counter start */
4774
4775         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4776         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4777     }
4778
4779     
4780     /* [HGM] logic here is completely changed. In stead of full positions */
4781     /* the initialized data only consist of the two backranks. The switch */
4782     /* selects which one we will use, which is than copied to the Board   */
4783     /* initialPosition, which for the rest is initialized by Pawns and    */
4784     /* empty squares. This initial position is then copied to boards[0],  */
4785     /* possibly after shuffling, so that it remains available.            */
4786
4787     gameInfo.holdingsWidth = 0; /* default board sizes */
4788     gameInfo.boardWidth    = 8;
4789     gameInfo.boardHeight   = 8;
4790     gameInfo.holdingsSize  = 0;
4791     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4792     for(i=0; i<BOARD_FILES-2; i++)
4793       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4794     initialPosition[EP_STATUS] = EP_NONE;
4795     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4796
4797     switch (gameInfo.variant) {
4798     case VariantFischeRandom:
4799       shuffleOpenings = TRUE;
4800     default:
4801       pieces = FIDEArray;
4802       break;
4803     case VariantShatranj:
4804       pieces = ShatranjArray;
4805       nrCastlingRights = 0;
4806       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4807       break;
4808     case VariantMakruk:
4809       pieces = makrukArray;
4810       nrCastlingRights = 0;
4811       startedFromSetupPosition = TRUE;
4812       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4813       break;
4814     case VariantTwoKings:
4815       pieces = twoKingsArray;
4816       break;
4817     case VariantCapaRandom:
4818       shuffleOpenings = TRUE;
4819     case VariantCapablanca:
4820       pieces = CapablancaArray;
4821       gameInfo.boardWidth = 10;
4822       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4823       break;
4824     case VariantGothic:
4825       pieces = GothicArray;
4826       gameInfo.boardWidth = 10;
4827       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4828       break;
4829     case VariantJanus:
4830       pieces = JanusArray;
4831       gameInfo.boardWidth = 10;
4832       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4833       nrCastlingRights = 6;
4834         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4835         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4836         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4837         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4838         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4839         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4840       break;
4841     case VariantFalcon:
4842       pieces = FalconArray;
4843       gameInfo.boardWidth = 10;
4844       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4845       break;
4846     case VariantXiangqi:
4847       pieces = XiangqiArray;
4848       gameInfo.boardWidth  = 9;
4849       gameInfo.boardHeight = 10;
4850       nrCastlingRights = 0;
4851       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4852       break;
4853     case VariantShogi:
4854       pieces = ShogiArray;
4855       gameInfo.boardWidth  = 9;
4856       gameInfo.boardHeight = 9;
4857       gameInfo.holdingsSize = 7;
4858       nrCastlingRights = 0;
4859       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4860       break;
4861     case VariantCourier:
4862       pieces = CourierArray;
4863       gameInfo.boardWidth  = 12;
4864       nrCastlingRights = 0;
4865       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4866       break;
4867     case VariantKnightmate:
4868       pieces = KnightmateArray;
4869       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4870       break;
4871     case VariantFairy:
4872       pieces = fairyArray;
4873       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4874       break;
4875     case VariantGreat:
4876       pieces = GreatArray;
4877       gameInfo.boardWidth = 10;
4878       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4879       gameInfo.holdingsSize = 8;
4880       break;
4881     case VariantSuper:
4882       pieces = FIDEArray;
4883       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4884       gameInfo.holdingsSize = 8;
4885       startedFromSetupPosition = TRUE;
4886       break;
4887     case VariantCrazyhouse:
4888     case VariantBughouse:
4889       pieces = FIDEArray;
4890       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4891       gameInfo.holdingsSize = 5;
4892       break;
4893     case VariantWildCastle:
4894       pieces = FIDEArray;
4895       /* !!?shuffle with kings guaranteed to be on d or e file */
4896       shuffleOpenings = 1;
4897       break;
4898     case VariantNoCastle:
4899       pieces = FIDEArray;
4900       nrCastlingRights = 0;
4901       /* !!?unconstrained back-rank shuffle */
4902       shuffleOpenings = 1;
4903       break;
4904     }
4905
4906     overrule = 0;
4907     if(appData.NrFiles >= 0) {
4908         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4909         gameInfo.boardWidth = appData.NrFiles;
4910     }
4911     if(appData.NrRanks >= 0) {
4912         gameInfo.boardHeight = appData.NrRanks;
4913     }
4914     if(appData.holdingsSize >= 0) {
4915         i = appData.holdingsSize;
4916         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4917         gameInfo.holdingsSize = i;
4918     }
4919     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4920     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4921         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4922
4923     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4924     if(pawnRow < 1) pawnRow = 1;
4925     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4926
4927     /* User pieceToChar list overrules defaults */
4928     if(appData.pieceToCharTable != NULL)
4929         SetCharTable(pieceToChar, appData.pieceToCharTable);
4930
4931     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4932
4933         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4934             s = (ChessSquare) 0; /* account holding counts in guard band */
4935         for( i=0; i<BOARD_HEIGHT; i++ )
4936             initialPosition[i][j] = s;
4937
4938         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4939         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4940         initialPosition[pawnRow][j] = WhitePawn;
4941         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4942         if(gameInfo.variant == VariantXiangqi) {
4943             if(j&1) {
4944                 initialPosition[pawnRow][j] = 
4945                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4946                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4947                    initialPosition[2][j] = WhiteCannon;
4948                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4949                 }
4950             }
4951         }
4952         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4953     }
4954     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4955
4956             j=BOARD_LEFT+1;
4957             initialPosition[1][j] = WhiteBishop;
4958             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4959             j=BOARD_RGHT-2;
4960             initialPosition[1][j] = WhiteRook;
4961             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4962     }
4963
4964     if( nrCastlingRights == -1) {
4965         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4966         /*       This sets default castling rights from none to normal corners   */
4967         /* Variants with other castling rights must set them themselves above    */
4968         nrCastlingRights = 6;
4969        
4970         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4971         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4972         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4973         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4974         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4975         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4976      }
4977
4978      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4979      if(gameInfo.variant == VariantGreat) { // promotion commoners
4980         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4981         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4982         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4983         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4984      }
4985   if (appData.debugMode) {
4986     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4987   }
4988     if(shuffleOpenings) {
4989         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4990         startedFromSetupPosition = TRUE;
4991     }
4992     if(startedFromPositionFile) {
4993       /* [HGM] loadPos: use PositionFile for every new game */
4994       CopyBoard(initialPosition, filePosition);
4995       for(i=0; i<nrCastlingRights; i++)
4996           initialRights[i] = filePosition[CASTLING][i];
4997       startedFromSetupPosition = TRUE;
4998     }
4999
5000     CopyBoard(boards[0], initialPosition);
5001
5002     if(oldx != gameInfo.boardWidth ||
5003        oldy != gameInfo.boardHeight ||
5004        oldh != gameInfo.holdingsWidth
5005 #ifdef GOTHIC
5006        || oldv == VariantGothic ||        // For licensing popups
5007        gameInfo.variant == VariantGothic
5008 #endif
5009 #ifdef FALCON
5010        || oldv == VariantFalcon ||
5011        gameInfo.variant == VariantFalcon
5012 #endif
5013                                          )
5014             InitDrawingSizes(-2 ,0);
5015
5016     if (redraw)
5017       DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 void
5021 SendBoard(cps, moveNum)
5022      ChessProgramState *cps;
5023      int moveNum;
5024 {
5025     char message[MSG_SIZ];
5026     
5027     if (cps->useSetboard) {
5028       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5029       sprintf(message, "setboard %s\n", fen);
5030       SendToProgram(message, cps);
5031       free(fen);
5032
5033     } else {
5034       ChessSquare *bp;
5035       int i, j;
5036       /* Kludge to set black to move, avoiding the troublesome and now
5037        * deprecated "black" command.
5038        */
5039       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5040
5041       SendToProgram("edit\n", cps);
5042       SendToProgram("#\n", cps);
5043       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5044         bp = &boards[moveNum][i][BOARD_LEFT];
5045         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5046           if ((int) *bp < (int) BlackPawn) {
5047             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5048                     AAA + j, ONE + i);
5049             if(message[0] == '+' || message[0] == '~') {
5050                 sprintf(message, "%c%c%c+\n",
5051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5052                         AAA + j, ONE + i);
5053             }
5054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5055                 message[1] = BOARD_RGHT   - 1 - j + '1';
5056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5057             }
5058             SendToProgram(message, cps);
5059           }
5060         }
5061       }
5062     
5063       SendToProgram("c\n", cps);
5064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5065         bp = &boards[moveNum][i][BOARD_LEFT];
5066         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5067           if (((int) *bp != (int) EmptySquare)
5068               && ((int) *bp >= (int) BlackPawn)) {
5069             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5070                     AAA + j, ONE + i);
5071             if(message[0] == '+' || message[0] == '~') {
5072                 sprintf(message, "%c%c%c+\n",
5073                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5074                         AAA + j, ONE + i);
5075             }
5076             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5077                 message[1] = BOARD_RGHT   - 1 - j + '1';
5078                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5079             }
5080             SendToProgram(message, cps);
5081           }
5082         }
5083       }
5084     
5085       SendToProgram(".\n", cps);
5086     }
5087     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5088 }
5089
5090 int
5091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5092 {
5093     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5094     /* [HGM] add Shogi promotions */
5095     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5096     ChessSquare piece;
5097     ChessMove moveType;
5098     Boolean premove;
5099
5100     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5101     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5102
5103     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5104       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5105         return FALSE;
5106
5107     piece = boards[currentMove][fromY][fromX];
5108     if(gameInfo.variant == VariantShogi) {
5109         promotionZoneSize = 3;
5110         highestPromotingPiece = (int)WhiteFerz;
5111     } else if(gameInfo.variant == VariantMakruk) {
5112         promotionZoneSize = 3;
5113     }
5114
5115     // next weed out all moves that do not touch the promotion zone at all
5116     if((int)piece >= BlackPawn) {
5117         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5118              return FALSE;
5119         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5120     } else {
5121         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5122            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5123     }
5124
5125     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5126
5127     // weed out mandatory Shogi promotions
5128     if(gameInfo.variant == VariantShogi) {
5129         if(piece >= BlackPawn) {
5130             if(toY == 0 && piece == BlackPawn ||
5131                toY == 0 && piece == BlackQueen ||
5132                toY <= 1 && piece == BlackKnight) {
5133                 *promoChoice = '+';
5134                 return FALSE;
5135             }
5136         } else {
5137             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5138                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5139                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5140                 *promoChoice = '+';
5141                 return FALSE;
5142             }
5143         }
5144     }
5145
5146     // weed out obviously illegal Pawn moves
5147     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5148         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5149         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5150         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5151         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5152         // note we are not allowed to test for valid (non-)capture, due to premove
5153     }
5154
5155     // we either have a choice what to promote to, or (in Shogi) whether to promote
5156     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5157         *promoChoice = PieceToChar(BlackFerz);  // no choice
5158         return FALSE;
5159     }
5160     if(appData.alwaysPromoteToQueen) { // predetermined
5161         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5162              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5163         else *promoChoice = PieceToChar(BlackQueen);
5164         return FALSE;
5165     }
5166
5167     // suppress promotion popup on illegal moves that are not premoves
5168     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5169               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5170     if(appData.testLegality && !premove) {
5171         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5172                         fromY, fromX, toY, toX, NULLCHAR);
5173         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5174            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5175             return FALSE;
5176     }
5177
5178     return TRUE;
5179 }
5180
5181 int
5182 InPalace(row, column)
5183      int row, column;
5184 {   /* [HGM] for Xiangqi */
5185     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5186          column < (BOARD_WIDTH + 4)/2 &&
5187          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5188     return FALSE;
5189 }
5190
5191 int
5192 PieceForSquare (x, y)
5193      int x;
5194      int y;
5195 {
5196   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5197      return -1;
5198   else
5199      return boards[currentMove][y][x];
5200 }
5201
5202 int
5203 OKToStartUserMove(x, y)
5204      int x, y;
5205 {
5206     ChessSquare from_piece;
5207     int white_piece;
5208
5209     if (matchMode) return FALSE;
5210     if (gameMode == EditPosition) return TRUE;
5211
5212     if (x >= 0 && y >= 0)
5213       from_piece = boards[currentMove][y][x];
5214     else
5215       from_piece = EmptySquare;
5216
5217     if (from_piece == EmptySquare) return FALSE;
5218
5219     white_piece = (int)from_piece >= (int)WhitePawn &&
5220       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5221
5222     switch (gameMode) {
5223       case PlayFromGameFile:
5224       case AnalyzeFile:
5225       case TwoMachinesPlay:
5226       case EndOfGame:
5227         return FALSE;
5228
5229       case IcsObserving:
5230       case IcsIdle:
5231         return FALSE;
5232
5233       case MachinePlaysWhite:
5234       case IcsPlayingBlack:
5235         if (appData.zippyPlay) return FALSE;
5236         if (white_piece) {
5237             DisplayMoveError(_("You are playing Black"));
5238             return FALSE;
5239         }
5240         break;
5241
5242       case MachinePlaysBlack:
5243       case IcsPlayingWhite:
5244         if (appData.zippyPlay) return FALSE;
5245         if (!white_piece) {
5246             DisplayMoveError(_("You are playing White"));
5247             return FALSE;
5248         }
5249         break;
5250
5251       case EditGame:
5252         if (!white_piece && WhiteOnMove(currentMove)) {
5253             DisplayMoveError(_("It is White's turn"));
5254             return FALSE;
5255         }           
5256         if (white_piece && !WhiteOnMove(currentMove)) {
5257             DisplayMoveError(_("It is Black's turn"));
5258             return FALSE;
5259         }           
5260         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5261             /* Editing correspondence game history */
5262             /* Could disallow this or prompt for confirmation */
5263             cmailOldMove = -1;
5264         }
5265         break;
5266
5267       case BeginningOfGame:
5268         if (appData.icsActive) return FALSE;
5269         if (!appData.noChessProgram) {
5270             if (!white_piece) {
5271                 DisplayMoveError(_("You are playing White"));
5272                 return FALSE;
5273             }
5274         }
5275         break;
5276         
5277       case Training:
5278         if (!white_piece && WhiteOnMove(currentMove)) {
5279             DisplayMoveError(_("It is White's turn"));
5280             return FALSE;
5281         }           
5282         if (white_piece && !WhiteOnMove(currentMove)) {
5283             DisplayMoveError(_("It is Black's turn"));
5284             return FALSE;
5285         }           
5286         break;
5287
5288       default:
5289       case IcsExamining:
5290         break;
5291     }
5292     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5293         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5294         && gameMode != AnalyzeFile && gameMode != Training) {
5295         DisplayMoveError(_("Displayed position is not current"));
5296         return FALSE;
5297     }
5298     return TRUE;
5299 }
5300
5301 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5302 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5303 int lastLoadGameUseList = FALSE;
5304 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5305 ChessMove lastLoadGameStart = (ChessMove) 0;
5306
5307 ChessMove
5308 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5309      int fromX, fromY, toX, toY;
5310      int promoChar;
5311      Boolean captureOwn;
5312 {
5313     ChessMove moveType;
5314     ChessSquare pdown, pup;
5315
5316     /* Check if the user is playing in turn.  This is complicated because we
5317        let the user "pick up" a piece before it is his turn.  So the piece he
5318        tried to pick up may have been captured by the time he puts it down!
5319        Therefore we use the color the user is supposed to be playing in this
5320        test, not the color of the piece that is currently on the starting
5321        square---except in EditGame mode, where the user is playing both
5322        sides; fortunately there the capture race can't happen.  (It can
5323        now happen in IcsExamining mode, but that's just too bad.  The user
5324        will get a somewhat confusing message in that case.)
5325        */
5326
5327     switch (gameMode) {
5328       case PlayFromGameFile:
5329       case AnalyzeFile:
5330       case TwoMachinesPlay:
5331       case EndOfGame:
5332       case IcsObserving:
5333       case IcsIdle:
5334         /* We switched into a game mode where moves are not accepted,
5335            perhaps while the mouse button was down. */
5336         return ImpossibleMove;
5337
5338       case MachinePlaysWhite:
5339         /* User is moving for Black */
5340         if (WhiteOnMove(currentMove)) {
5341             DisplayMoveError(_("It is White's turn"));
5342             return ImpossibleMove;
5343         }
5344         break;
5345
5346       case MachinePlaysBlack:
5347         /* User is moving for White */
5348         if (!WhiteOnMove(currentMove)) {
5349             DisplayMoveError(_("It is Black's turn"));
5350             return ImpossibleMove;
5351         }
5352         break;
5353
5354       case EditGame:
5355       case IcsExamining:
5356       case BeginningOfGame:
5357       case AnalyzeMode:
5358       case Training:
5359         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5360             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5361             /* User is moving for Black */
5362             if (WhiteOnMove(currentMove)) {
5363                 DisplayMoveError(_("It is White's turn"));
5364                 return ImpossibleMove;
5365             }
5366         } else {
5367             /* User is moving for White */
5368             if (!WhiteOnMove(currentMove)) {
5369                 DisplayMoveError(_("It is Black's turn"));
5370                 return ImpossibleMove;
5371             }
5372         }
5373         break;
5374
5375       case IcsPlayingBlack:
5376         /* User is moving for Black */
5377         if (WhiteOnMove(currentMove)) {
5378             if (!appData.premove) {
5379                 DisplayMoveError(_("It is White's turn"));
5380             } else if (toX >= 0 && toY >= 0) {
5381                 premoveToX = toX;
5382                 premoveToY = toY;
5383                 premoveFromX = fromX;
5384                 premoveFromY = fromY;
5385                 premovePromoChar = promoChar;
5386                 gotPremove = 1;
5387                 if (appData.debugMode) 
5388                     fprintf(debugFP, "Got premove: fromX %d,"
5389                             "fromY %d, toX %d, toY %d\n",
5390                             fromX, fromY, toX, toY);
5391             }
5392             return ImpossibleMove;
5393         }
5394         break;
5395
5396       case IcsPlayingWhite:
5397         /* User is moving for White */
5398         if (!WhiteOnMove(currentMove)) {
5399             if (!appData.premove) {
5400                 DisplayMoveError(_("It is Black's turn"));
5401             } else if (toX >= 0 && toY >= 0) {
5402                 premoveToX = toX;
5403                 premoveToY = toY;
5404                 premoveFromX = fromX;
5405                 premoveFromY = fromY;
5406                 premovePromoChar = promoChar;
5407                 gotPremove = 1;
5408                 if (appData.debugMode) 
5409                     fprintf(debugFP, "Got premove: fromX %d,"
5410                             "fromY %d, toX %d, toY %d\n",
5411                             fromX, fromY, toX, toY);
5412             }
5413             return ImpossibleMove;
5414         }
5415         break;
5416
5417       default:
5418         break;
5419
5420       case EditPosition:
5421         /* EditPosition, empty square, or different color piece;
5422            click-click move is possible */
5423         if (toX == -2 || toY == -2) {
5424             boards[0][fromY][fromX] = EmptySquare;
5425             return AmbiguousMove;
5426         } else if (toX >= 0 && toY >= 0) {
5427             boards[0][toY][toX] = boards[0][fromY][fromX];
5428             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5429                 if(boards[0][fromY][0] != EmptySquare) {
5430                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5431                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5432                 }
5433             } else
5434             if(fromX == BOARD_RGHT+1) {
5435                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5436                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5437                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5438                 }
5439             } else
5440             boards[0][fromY][fromX] = EmptySquare;
5441             return AmbiguousMove;
5442         }
5443         return ImpossibleMove;
5444     }
5445
5446     if(toX < 0 || toY < 0) return ImpossibleMove;
5447     pdown = boards[currentMove][fromY][fromX];
5448     pup = boards[currentMove][toY][toX];
5449
5450     /* [HGM] If move started in holdings, it means a drop */
5451     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5452          if( pup != EmptySquare ) return ImpossibleMove;
5453          if(appData.testLegality) {
5454              /* it would be more logical if LegalityTest() also figured out
5455               * which drops are legal. For now we forbid pawns on back rank.
5456               * Shogi is on its own here...
5457               */
5458              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5459                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5460                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5461          }
5462          return WhiteDrop; /* Not needed to specify white or black yet */
5463     }
5464
5465     /* [HGM] always test for legality, to get promotion info */
5466     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5467                                          fromY, fromX, toY, toX, promoChar);
5468     /* [HGM] but possibly ignore an IllegalMove result */
5469     if (appData.testLegality) {
5470         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5471             DisplayMoveError(_("Illegal move"));
5472             return ImpossibleMove;
5473         }
5474     }
5475
5476     return moveType;
5477     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5478        function is made into one that returns an OK move type if FinishMove
5479        should be called. This to give the calling driver routine the
5480        opportunity to finish the userMove input with a promotion popup,
5481        without bothering the user with this for invalid or illegal moves */
5482
5483 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5484 }
5485
5486 /* Common tail of UserMoveEvent and DropMenuEvent */
5487 int
5488 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5489      ChessMove moveType;
5490      int fromX, fromY, toX, toY;
5491      /*char*/int promoChar;
5492 {
5493     char *bookHit = 0;
5494
5495     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5496         // [HGM] superchess: suppress promotions to non-available piece
5497         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5498         if(WhiteOnMove(currentMove)) {
5499             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5500         } else {
5501             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5502         }
5503     }
5504
5505     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5506        move type in caller when we know the move is a legal promotion */
5507     if(moveType == NormalMove && promoChar)
5508         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5509
5510     /* [HGM] convert drag-and-drop piece drops to standard form */
5511     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5512          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5513            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5514                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5515            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5516            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5517            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5518            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5519          fromY = DROP_RANK;
5520     }
5521
5522     /* [HGM] <popupFix> The following if has been moved here from
5523        UserMoveEvent(). Because it seemed to belong here (why not allow
5524        piece drops in training games?), and because it can only be
5525        performed after it is known to what we promote. */
5526     if (gameMode == Training) {
5527       /* compare the move played on the board to the next move in the
5528        * game. If they match, display the move and the opponent's response. 
5529        * If they don't match, display an error message.
5530        */
5531       int saveAnimate;
5532       Board testBoard;
5533       CopyBoard(testBoard, boards[currentMove]);
5534       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5535
5536       if (CompareBoards(testBoard, boards[currentMove+1])) {
5537         ForwardInner(currentMove+1);
5538
5539         /* Autoplay the opponent's response.
5540          * if appData.animate was TRUE when Training mode was entered,
5541          * the response will be animated.
5542          */
5543         saveAnimate = appData.animate;
5544         appData.animate = animateTraining;
5545         ForwardInner(currentMove+1);
5546         appData.animate = saveAnimate;
5547
5548         /* check for the end of the game */
5549         if (currentMove >= forwardMostMove) {
5550           gameMode = PlayFromGameFile;
5551           ModeHighlight();
5552           SetTrainingModeOff();
5553           DisplayInformation(_("End of game"));
5554         }
5555       } else {
5556         DisplayError(_("Incorrect move"), 0);
5557       }
5558       return 1;
5559     }
5560
5561   /* Ok, now we know that the move is good, so we can kill
5562      the previous line in Analysis Mode */
5563   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5564                                 && currentMove < forwardMostMove) {
5565     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5566   }
5567
5568   /* If we need the chess program but it's dead, restart it */
5569   ResurrectChessProgram();
5570
5571   /* A user move restarts a paused game*/
5572   if (pausing)
5573     PauseEvent();
5574
5575   thinkOutput[0] = NULLCHAR;
5576
5577   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5578
5579   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5580
5581   if (gameMode == BeginningOfGame) {
5582     if (appData.noChessProgram) {
5583       gameMode = EditGame;
5584       SetGameInfo();
5585     } else {
5586       char buf[MSG_SIZ];
5587       gameMode = MachinePlaysBlack;
5588       StartClocks();
5589       SetGameInfo();
5590       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5591       DisplayTitle(buf);
5592       if (first.sendName) {
5593         sprintf(buf, "name %s\n", gameInfo.white);
5594         SendToProgram(buf, &first);
5595       }
5596       StartClocks();
5597     }
5598     ModeHighlight();
5599   }
5600
5601   /* Relay move to ICS or chess engine */
5602   if (appData.icsActive) {
5603     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5604         gameMode == IcsExamining) {
5605       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5606         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5607         SendToICS("draw ");
5608         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5609       }
5610       // also send plain move, in case ICS does not understand atomic claims
5611       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5612       ics_user_moved = 1;
5613     }
5614   } else {
5615     if (first.sendTime && (gameMode == BeginningOfGame ||
5616                            gameMode == MachinePlaysWhite ||
5617                            gameMode == MachinePlaysBlack)) {
5618       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5619     }
5620     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5621          // [HGM] book: if program might be playing, let it use book
5622         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5623         first.maybeThinking = TRUE;
5624     } else SendMoveToProgram(forwardMostMove-1, &first);
5625     if (currentMove == cmailOldMove + 1) {
5626       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5627     }
5628   }
5629
5630   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5631
5632   switch (gameMode) {
5633   case EditGame:
5634     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5635     case MT_NONE:
5636     case MT_CHECK:
5637       break;
5638     case MT_CHECKMATE:
5639     case MT_STAINMATE:
5640       if (WhiteOnMove(currentMove)) {
5641         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5642       } else {
5643         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5644       }
5645       break;
5646     case MT_STALEMATE:
5647       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5648       break;
5649     }
5650     break;
5651     
5652   case MachinePlaysBlack:
5653   case MachinePlaysWhite:
5654     /* disable certain menu options while machine is thinking */
5655     SetMachineThinkingEnables();
5656     break;
5657
5658   default:
5659     break;
5660   }
5661
5662   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5663         
5664   if(bookHit) { // [HGM] book: simulate book reply
5665         static char bookMove[MSG_SIZ]; // a bit generous?
5666
5667         programStats.nodes = programStats.depth = programStats.time = 
5668         programStats.score = programStats.got_only_move = 0;
5669         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5670
5671         strcpy(bookMove, "move ");
5672         strcat(bookMove, bookHit);
5673         HandleMachineMove(bookMove, &first);
5674   }
5675   return 1;
5676 }
5677
5678 void
5679 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5680      int fromX, fromY, toX, toY;
5681      int promoChar;
5682 {
5683     /* [HGM] This routine was added to allow calling of its two logical
5684        parts from other modules in the old way. Before, UserMoveEvent()
5685        automatically called FinishMove() if the move was OK, and returned
5686        otherwise. I separated the two, in order to make it possible to
5687        slip a promotion popup in between. But that it always needs two
5688        calls, to the first part, (now called UserMoveTest() ), and to
5689        FinishMove if the first part succeeded. Calls that do not need
5690        to do anything in between, can call this routine the old way. 
5691     */
5692     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5693 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5694     if(moveType == AmbiguousMove)
5695         DrawPosition(FALSE, boards[currentMove]);
5696     else if(moveType != ImpossibleMove && moveType != Comment)
5697         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5698 }
5699
5700 void
5701 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5702      Board board;
5703      int flags;
5704      ChessMove kind;
5705      int rf, ff, rt, ft;
5706      VOIDSTAR closure;
5707 {
5708     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5709     Markers *m = (Markers *) closure;
5710     if(rf == fromY && ff == fromX)
5711         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5712                          || kind == WhiteCapturesEnPassant
5713                          || kind == BlackCapturesEnPassant);
5714     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5715 }
5716
5717 void
5718 MarkTargetSquares(int clear)
5719 {
5720   int x, y;
5721   if(!appData.markers || !appData.highlightDragging || 
5722      !appData.testLegality || gameMode == EditPosition) return;
5723   if(clear) {
5724     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5725   } else {
5726     int capt = 0;
5727     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5728     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5729       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5730       if(capt)
5731       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5732     }
5733   }
5734   DrawPosition(TRUE, NULL);
5735 }
5736
5737 void LeftClick(ClickType clickType, int xPix, int yPix)
5738 {
5739     int x, y;
5740     Boolean saveAnimate;
5741     static int second = 0, promotionChoice = 0;
5742     char promoChoice = NULLCHAR;
5743
5744     if (clickType == Press) ErrorPopDown();
5745     MarkTargetSquares(1);
5746
5747     x = EventToSquare(xPix, BOARD_WIDTH);
5748     y = EventToSquare(yPix, BOARD_HEIGHT);
5749     if (!flipView && y >= 0) {
5750         y = BOARD_HEIGHT - 1 - y;
5751     }
5752     if (flipView && x >= 0) {
5753         x = BOARD_WIDTH - 1 - x;
5754     }
5755
5756     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5757         if(clickType == Release) return; // ignore upclick of click-click destination
5758         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5759         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5760         if(gameInfo.holdingsWidth && 
5761                 (WhiteOnMove(currentMove) 
5762                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5763                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5764             // click in right holdings, for determining promotion piece
5765             ChessSquare p = boards[currentMove][y][x];
5766             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5767             if(p != EmptySquare) {
5768                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5769                 fromX = fromY = -1;
5770                 return;
5771             }
5772         }
5773         DrawPosition(FALSE, boards[currentMove]);
5774         return;
5775     }
5776
5777     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5778     if(clickType == Press
5779             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5780               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5781               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5782         return;
5783
5784     if (fromX == -1) {
5785         if (clickType == Press) {
5786             /* First square */
5787             if (OKToStartUserMove(x, y)) {
5788                 fromX = x;
5789                 fromY = y;
5790                 second = 0;
5791                 MarkTargetSquares(0);
5792                 DragPieceBegin(xPix, yPix);
5793                 if (appData.highlightDragging) {
5794                     SetHighlights(x, y, -1, -1);
5795                 }
5796             }
5797         }
5798         return;
5799     }
5800
5801     /* fromX != -1 */
5802     if (clickType == Press && gameMode != EditPosition) {
5803         ChessSquare fromP;
5804         ChessSquare toP;
5805         int frc;
5806
5807         // ignore off-board to clicks
5808         if(y < 0 || x < 0) return;
5809
5810         /* Check if clicking again on the same color piece */
5811         fromP = boards[currentMove][fromY][fromX];
5812         toP = boards[currentMove][y][x];
5813         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5814         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5815              WhitePawn <= toP && toP <= WhiteKing &&
5816              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5817              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5818             (BlackPawn <= fromP && fromP <= BlackKing && 
5819              BlackPawn <= toP && toP <= BlackKing &&
5820              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5821              !(fromP == BlackKing && toP == BlackRook && frc))) {
5822             /* Clicked again on same color piece -- changed his mind */
5823             second = (x == fromX && y == fromY);
5824             if (appData.highlightDragging) {
5825                 SetHighlights(x, y, -1, -1);
5826             } else {
5827                 ClearHighlights();
5828             }
5829             if (OKToStartUserMove(x, y)) {
5830                 fromX = x;
5831                 fromY = y;
5832                 MarkTargetSquares(0);
5833                 DragPieceBegin(xPix, yPix);
5834             }
5835             return;
5836         }
5837         // ignore clicks on holdings
5838         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5839     }
5840
5841     if (clickType == Release && x == fromX && y == fromY) {
5842         DragPieceEnd(xPix, yPix);
5843         if (appData.animateDragging) {
5844             /* Undo animation damage if any */
5845             DrawPosition(FALSE, NULL);
5846         }
5847         if (second) {
5848             /* Second up/down in same square; just abort move */
5849             second = 0;
5850             fromX = fromY = -1;
5851             ClearHighlights();
5852             gotPremove = 0;
5853             ClearPremoveHighlights();
5854         } else {
5855             /* First upclick in same square; start click-click mode */
5856             SetHighlights(x, y, -1, -1);
5857         }
5858         return;
5859     }
5860
5861     /* we now have a different from- and (possibly off-board) to-square */
5862     /* Completed move */
5863     toX = x;
5864     toY = y;
5865     saveAnimate = appData.animate;
5866     if (clickType == Press) {
5867         /* Finish clickclick move */
5868         if (appData.animate || appData.highlightLastMove) {
5869             SetHighlights(fromX, fromY, toX, toY);
5870         } else {
5871             ClearHighlights();
5872         }
5873     } else {
5874         /* Finish drag move */
5875         if (appData.highlightLastMove) {
5876             SetHighlights(fromX, fromY, toX, toY);
5877         } else {
5878             ClearHighlights();
5879         }
5880         DragPieceEnd(xPix, yPix);
5881         /* Don't animate move and drag both */
5882         appData.animate = FALSE;
5883     }
5884
5885     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5886     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5887         ChessSquare piece = boards[currentMove][fromY][fromX];
5888         if(gameMode == EditPosition && piece != EmptySquare &&
5889            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5890             int n;
5891              
5892             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5893                 n = PieceToNumber(piece - (int)BlackPawn);
5894                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5895                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5896                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5897             } else
5898             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5899                 n = PieceToNumber(piece);
5900                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5901                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5902                 boards[currentMove][n][BOARD_WIDTH-2]++;
5903             }
5904             boards[currentMove][fromY][fromX] = EmptySquare;
5905         }
5906         ClearHighlights();
5907         fromX = fromY = -1;
5908         DrawPosition(TRUE, boards[currentMove]);
5909         return;
5910     }
5911
5912     // off-board moves should not be highlighted
5913     if(x < 0 || x < 0) ClearHighlights();
5914
5915     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5916         SetHighlights(fromX, fromY, toX, toY);
5917         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5918             // [HGM] super: promotion to captured piece selected from holdings
5919             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5920             promotionChoice = TRUE;
5921             // kludge follows to temporarily execute move on display, without promoting yet
5922             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5923             boards[currentMove][toY][toX] = p;
5924             DrawPosition(FALSE, boards[currentMove]);
5925             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5926             boards[currentMove][toY][toX] = q;
5927             DisplayMessage("Click in holdings to choose piece", "");
5928             return;
5929         }
5930         PromotionPopUp();
5931     } else {
5932         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5933         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5934         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5935         fromX = fromY = -1;
5936     }
5937     appData.animate = saveAnimate;
5938     if (appData.animate || appData.animateDragging) {
5939         /* Undo animation damage if needed */
5940         DrawPosition(FALSE, NULL);
5941     }
5942 }
5943
5944 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
5945 {   // front-end-free part taken out of PieceMenuPopup
5946     int whichMenu; int xSqr, ySqr;
5947
5948     xSqr = EventToSquare(x, BOARD_WIDTH);
5949     ySqr = EventToSquare(y, BOARD_HEIGHT);
5950     if (action == Release) UnLoadPV(); // [HGM] pv
5951     if (action != Press) return -2;
5952     switch (gameMode) {
5953       case IcsExamining:
5954         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
5955       case EditPosition:
5956         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
5957         if (xSqr < 0 || ySqr < 0) return -1;\r
5958         whichMenu = 0; // edit-position menu
5959         break;
5960       case IcsObserving:
5961         if(!appData.icsEngineAnalyze) return -1;
5962       case IcsPlayingWhite:
5963       case IcsPlayingBlack:
5964         if(!appData.zippyPlay) goto noZip;
5965       case AnalyzeMode:
5966       case AnalyzeFile:
5967       case MachinePlaysWhite:
5968       case MachinePlaysBlack:
5969       case TwoMachinesPlay: // [HGM] pv: use for showing PV
5970         if (!appData.dropMenu) {
5971           LoadPV(x, y);
5972           return 2; // flag front-end to grab mouse events
5973         }
5974         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
5975            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
5976       case EditGame:
5977       noZip:
5978         if (xSqr < 0 || ySqr < 0) return -1;
5979         if (!appData.dropMenu || appData.testLegality &&
5980             gameInfo.variant != VariantBughouse &&
5981             gameInfo.variant != VariantCrazyhouse) return -1;
5982         whichMenu = 1; // drop menu
5983         break;
5984       default:
5985         return -1;
5986     }
5987
5988     if (((*fromX = xSqr) < 0) ||
5989         ((*fromY = ySqr) < 0)) {
5990         *fromX = *fromY = -1;
5991         return -1;
5992     }
5993     if (flipView)
5994       *fromX = BOARD_WIDTH - 1 - *fromX;
5995     else
5996       *fromY = BOARD_HEIGHT - 1 - *fromY;
5997
5998     return whichMenu;
5999 }
6000
6001 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6002 {
6003 //    char * hint = lastHint;
6004     FrontEndProgramStats stats;
6005
6006     stats.which = cps == &first ? 0 : 1;
6007     stats.depth = cpstats->depth;
6008     stats.nodes = cpstats->nodes;
6009     stats.score = cpstats->score;
6010     stats.time = cpstats->time;
6011     stats.pv = cpstats->movelist;
6012     stats.hint = lastHint;
6013     stats.an_move_index = 0;
6014     stats.an_move_count = 0;
6015
6016     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6017         stats.hint = cpstats->move_name;
6018         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6019         stats.an_move_count = cpstats->nr_moves;
6020     }
6021
6022     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6023
6024     SetProgramStats( &stats );
6025 }
6026
6027 int
6028 Adjudicate(ChessProgramState *cps)
6029 {       // [HGM] some adjudications useful with buggy engines
6030         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6031         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6032         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6033         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6034         int k, count = 0; static int bare = 1;
6035         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6036         Boolean canAdjudicate = !appData.icsActive;
6037
6038         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6039         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6040             if( appData.testLegality )
6041             {   /* [HGM] Some more adjudications for obstinate engines */
6042                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6043                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6044                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6045                 static int moveCount = 6;
6046                 ChessMove result;
6047                 char *reason = NULL;
6048
6049                 /* Count what is on board. */
6050                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6051                 {   ChessSquare p = boards[forwardMostMove][i][j];
6052                     int m=i;
6053
6054                     switch((int) p)
6055                     {   /* count B,N,R and other of each side */
6056                         case WhiteKing:
6057                         case BlackKing:
6058                              NrK++; break; // [HGM] atomic: count Kings
6059                         case WhiteKnight:
6060                              NrWN++; break;
6061                         case WhiteBishop:
6062                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6063                              bishopsColor |= 1 << ((i^j)&1);
6064                              NrWB++; break;
6065                         case BlackKnight:
6066                              NrBN++; break;
6067                         case BlackBishop:
6068                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6069                              bishopsColor |= 1 << ((i^j)&1);
6070                              NrBB++; break;
6071                         case WhiteRook:
6072                              NrWR++; break;
6073                         case BlackRook:
6074                              NrBR++; break;
6075                         case WhiteQueen:
6076                              NrWQ++; break;
6077                         case BlackQueen:
6078                              NrBQ++; break;
6079                         case EmptySquare: 
6080                              break;
6081                         case BlackPawn:
6082                              m = 7-i;
6083                         case WhitePawn:
6084                              PawnAdvance += m; NrPawns++;
6085                     }
6086                     NrPieces += (p != EmptySquare);
6087                     NrW += ((int)p < (int)BlackPawn);
6088                     if(gameInfo.variant == VariantXiangqi && 
6089                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6090                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6091                         NrW -= ((int)p < (int)BlackPawn);
6092                     }
6093                 }
6094
6095                 /* Some material-based adjudications that have to be made before stalemate test */
6096                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6097                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6098                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6099                      if(canAdjudicate && appData.checkMates) {
6100                          if(engineOpponent)
6101                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6102                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6103                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6104                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6105                          return 1;
6106                      }
6107                 }
6108
6109                 /* Bare King in Shatranj (loses) or Losers (wins) */
6110                 if( NrW == 1 || NrPieces - NrW == 1) {
6111                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6112                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6113                      if(canAdjudicate && appData.checkMates) {
6114                          if(engineOpponent)
6115                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6116                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6117                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6118                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6119                          return 1;
6120                      }
6121                   } else
6122                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6123                   {    /* bare King */
6124                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6125                         if(canAdjudicate && appData.checkMates) {
6126                             /* but only adjudicate if adjudication enabled */
6127                             if(engineOpponent)
6128                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6129                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6130                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6131                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6132                             return 1;
6133                         }
6134                   }
6135                 } else bare = 1;
6136
6137
6138             // don't wait for engine to announce game end if we can judge ourselves
6139             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6140               case MT_CHECK:
6141                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6142                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6143                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6144                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6145                             checkCnt++;
6146                         if(checkCnt >= 2) {
6147                             reason = "Xboard adjudication: 3rd check";
6148                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6149                             break;
6150                         }
6151                     }
6152                 }
6153               case MT_NONE:
6154               default:
6155                 break;
6156               case MT_STALEMATE:
6157               case MT_STAINMATE:
6158                 reason = "Xboard adjudication: Stalemate";
6159                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6160                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6161                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6162                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6163                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6164                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6165                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6166                                                                         EP_CHECKMATE : EP_WINS);
6167                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6168                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6169                 }
6170                 break;
6171               case MT_CHECKMATE:
6172                 reason = "Xboard adjudication: Checkmate";
6173                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6174                 break;
6175             }
6176
6177                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6178                     case EP_STALEMATE:
6179                         result = GameIsDrawn; break;
6180                     case EP_CHECKMATE:
6181                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6182                     case EP_WINS:
6183                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6184                     default:
6185                         result = (ChessMove) 0;
6186                 }
6187                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6188                     if(engineOpponent)
6189                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6190                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6191                     GameEnds( result, reason, GE_XBOARD );
6192                     return 1;
6193                 }
6194
6195                 /* Next absolutely insufficient mating material. */
6196                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6197                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6198                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6199                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6200                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6201
6202                      /* always flag draws, for judging claims */
6203                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6204
6205                      if(canAdjudicate && appData.materialDraws) {
6206                          /* but only adjudicate them if adjudication enabled */
6207                          if(engineOpponent) {
6208                            SendToProgram("force\n", engineOpponent); // suppress reply
6209                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6210                          }
6211                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6212                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6213                          return 1;
6214                      }
6215                 }
6216
6217                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6218                 if(NrPieces == 4 && 
6219                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6220                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6221                    || NrWN==2 || NrBN==2     /* KNNK */
6222                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6223                   ) ) {
6224                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6225                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6226                           if(engineOpponent) {
6227                             SendToProgram("force\n", engineOpponent); // suppress reply
6228                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6229                           }
6230                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6231                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6232                           return 1;
6233                      }
6234                 } else moveCount = 6;
6235             }
6236         }
6237           
6238         if (appData.debugMode) { int i;
6239             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6240                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6241                     appData.drawRepeats);
6242             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6243               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6244             
6245         }
6246
6247         // Repetition draws and 50-move rule can be applied independently of legality testing
6248
6249                 /* Check for rep-draws */
6250                 count = 0;
6251                 for(k = forwardMostMove-2;
6252                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6253                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6254                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6255                     k-=2)
6256                 {   int rights=0;
6257                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6258                         /* compare castling rights */
6259                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6260                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6261                                 rights++; /* King lost rights, while rook still had them */
6262                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6263                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6264                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6265                                    rights++; /* but at least one rook lost them */
6266                         }
6267                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6268                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6269                                 rights++; 
6270                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6271                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6272                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6273                                    rights++;
6274                         }
6275                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6276                             && appData.drawRepeats > 1) {
6277                              /* adjudicate after user-specified nr of repeats */
6278                              if(engineOpponent) {
6279                                SendToProgram("force\n", engineOpponent); // suppress reply
6280                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6281                              }
6282                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6283                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6284                                 // [HGM] xiangqi: check for forbidden perpetuals
6285                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6286                                 for(m=forwardMostMove; m>k; m-=2) {
6287                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6288                                         ourPerpetual = 0; // the current mover did not always check
6289                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6290                                         hisPerpetual = 0; // the opponent did not always check
6291                                 }
6292                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6293                                                                         ourPerpetual, hisPerpetual);
6294                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6295                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6296                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6297                                     return 1;
6298                                 }
6299                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6300                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6301                                 // Now check for perpetual chases
6302                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6303                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6304                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6305                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6306                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6307                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6308                                         return 1;
6309                                     }
6310                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6311                                         break; // Abort repetition-checking loop.
6312                                 }
6313                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6314                              }
6315                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6316                              return 1;
6317                         }
6318                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6319                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6320                     }
6321                 }
6322
6323                 /* Now we test for 50-move draws. Determine ply count */
6324                 count = forwardMostMove;
6325                 /* look for last irreversble move */
6326                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6327                     count--;
6328                 /* if we hit starting position, add initial plies */
6329                 if( count == backwardMostMove )
6330                     count -= initialRulePlies;
6331                 count = forwardMostMove - count; 
6332                 if( count >= 100)
6333                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6334                          /* this is used to judge if draw claims are legal */
6335                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6336                          if(engineOpponent) {
6337                            SendToProgram("force\n", engineOpponent); // suppress reply
6338                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6339                          }
6340                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6341                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6342                          return 1;
6343                 }
6344
6345                 /* if draw offer is pending, treat it as a draw claim
6346                  * when draw condition present, to allow engines a way to
6347                  * claim draws before making their move to avoid a race
6348                  * condition occurring after their move
6349                  */
6350                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6351                          char *p = NULL;
6352                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6353                              p = "Draw claim: 50-move rule";
6354                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6355                              p = "Draw claim: 3-fold repetition";
6356                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6357                              p = "Draw claim: insufficient mating material";
6358                          if( p != NULL && canAdjudicate) {
6359                              if(engineOpponent) {
6360                                SendToProgram("force\n", engineOpponent); // suppress reply
6361                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6362                              }
6363                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6365                              return 1;
6366                          }
6367                 }
6368
6369                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6370                     if(engineOpponent) {
6371                       SendToProgram("force\n", engineOpponent); // suppress reply
6372                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6373                     }
6374                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6375                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6376                     return 1;
6377                 }
6378         return 0;
6379 }
6380
6381 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6382 {   // [HGM] book: this routine intercepts moves to simulate book replies
6383     char *bookHit = NULL;
6384
6385     //first determine if the incoming move brings opponent into his book
6386     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6387         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6388     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6389     if(bookHit != NULL && !cps->bookSuspend) {
6390         // make sure opponent is not going to reply after receiving move to book position
6391         SendToProgram("force\n", cps);
6392         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6393     }
6394     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6395     // now arrange restart after book miss
6396     if(bookHit) {
6397         // after a book hit we never send 'go', and the code after the call to this routine
6398         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6399         char buf[MSG_SIZ];
6400         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6401         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6402         SendToProgram(buf, cps);
6403         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6404     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6405         SendToProgram("go\n", cps);
6406         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6407     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6408         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6409             SendToProgram("go\n", cps); 
6410         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6411     }
6412     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6413 }
6414
6415 char *savedMessage;
6416 ChessProgramState *savedState;
6417 void DeferredBookMove(void)
6418 {
6419         if(savedState->lastPing != savedState->lastPong)
6420                     ScheduleDelayedEvent(DeferredBookMove, 10);
6421         else
6422         HandleMachineMove(savedMessage, savedState);
6423 }
6424
6425 void
6426 HandleMachineMove(message, cps)
6427      char *message;
6428      ChessProgramState *cps;
6429 {
6430     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6431     char realname[MSG_SIZ];
6432     int fromX, fromY, toX, toY;
6433     ChessMove moveType;
6434     char promoChar;
6435     char *p;
6436     int machineWhite;
6437     char *bookHit;
6438
6439     cps->userError = 0;
6440
6441 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6442     /*
6443      * Kludge to ignore BEL characters
6444      */
6445     while (*message == '\007') message++;
6446
6447     /*
6448      * [HGM] engine debug message: ignore lines starting with '#' character
6449      */
6450     if(cps->debug && *message == '#') return;
6451
6452     /*
6453      * Look for book output
6454      */
6455     if (cps == &first && bookRequested) {
6456         if (message[0] == '\t' || message[0] == ' ') {
6457             /* Part of the book output is here; append it */
6458             strcat(bookOutput, message);
6459             strcat(bookOutput, "  \n");
6460             return;
6461         } else if (bookOutput[0] != NULLCHAR) {
6462             /* All of book output has arrived; display it */
6463             char *p = bookOutput;
6464             while (*p != NULLCHAR) {
6465                 if (*p == '\t') *p = ' ';
6466                 p++;
6467             }
6468             DisplayInformation(bookOutput);
6469             bookRequested = FALSE;
6470             /* Fall through to parse the current output */
6471         }
6472     }
6473
6474     /*
6475      * Look for machine move.
6476      */
6477     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6478         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6479     {
6480         /* This method is only useful on engines that support ping */
6481         if (cps->lastPing != cps->lastPong) {
6482           if (gameMode == BeginningOfGame) {
6483             /* Extra move from before last new; ignore */
6484             if (appData.debugMode) {
6485                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6486             }
6487           } else {
6488             if (appData.debugMode) {
6489                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6490                         cps->which, gameMode);
6491             }
6492
6493             SendToProgram("undo\n", cps);
6494           }
6495           return;
6496         }
6497
6498         switch (gameMode) {
6499           case BeginningOfGame:
6500             /* Extra move from before last reset; ignore */
6501             if (appData.debugMode) {
6502                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6503             }
6504             return;
6505
6506           case EndOfGame:
6507           case IcsIdle:
6508           default:
6509             /* Extra move after we tried to stop.  The mode test is
6510                not a reliable way of detecting this problem, but it's
6511                the best we can do on engines that don't support ping.
6512             */
6513             if (appData.debugMode) {
6514                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6515                         cps->which, gameMode);
6516             }
6517             SendToProgram("undo\n", cps);
6518             return;
6519
6520           case MachinePlaysWhite:
6521           case IcsPlayingWhite:
6522             machineWhite = TRUE;
6523             break;
6524
6525           case MachinePlaysBlack:
6526           case IcsPlayingBlack:
6527             machineWhite = FALSE;
6528             break;
6529
6530           case TwoMachinesPlay:
6531             machineWhite = (cps->twoMachinesColor[0] == 'w');
6532             break;
6533         }
6534         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6535             if (appData.debugMode) {
6536                 fprintf(debugFP,
6537                         "Ignoring move out of turn by %s, gameMode %d"
6538                         ", forwardMost %d\n",
6539                         cps->which, gameMode, forwardMostMove);
6540             }
6541             return;
6542         }
6543
6544     if (appData.debugMode) { int f = forwardMostMove;
6545         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6546                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6547                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6548     }
6549         if(cps->alphaRank) AlphaRank(machineMove, 4);
6550         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6551                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6552             /* Machine move could not be parsed; ignore it. */
6553             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6554                     machineMove, cps->which);
6555             DisplayError(buf1, 0);
6556             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6557                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6558             if (gameMode == TwoMachinesPlay) {
6559               GameEnds(machineWhite ? BlackWins : WhiteWins,
6560                        buf1, GE_XBOARD);
6561             }
6562             return;
6563         }
6564
6565         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6566         /* So we have to redo legality test with true e.p. status here,  */
6567         /* to make sure an illegal e.p. capture does not slip through,   */
6568         /* to cause a forfeit on a justified illegal-move complaint      */
6569         /* of the opponent.                                              */
6570         if( gameMode==TwoMachinesPlay && appData.testLegality
6571             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6572                                                               ) {
6573            ChessMove moveType;
6574            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6575                              fromY, fromX, toY, toX, promoChar);
6576             if (appData.debugMode) {
6577                 int i;
6578                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6579                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6580                 fprintf(debugFP, "castling rights\n");
6581             }
6582             if(moveType == IllegalMove) {
6583                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6584                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6585                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6586                            buf1, GE_XBOARD);
6587                 return;
6588            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6589            /* [HGM] Kludge to handle engines that send FRC-style castling
6590               when they shouldn't (like TSCP-Gothic) */
6591            switch(moveType) {
6592              case WhiteASideCastleFR:
6593              case BlackASideCastleFR:
6594                toX+=2;
6595                currentMoveString[2]++;
6596                break;
6597              case WhiteHSideCastleFR:
6598              case BlackHSideCastleFR:
6599                toX--;
6600                currentMoveString[2]--;
6601                break;
6602              default: ; // nothing to do, but suppresses warning of pedantic compilers
6603            }
6604         }
6605         hintRequested = FALSE;
6606         lastHint[0] = NULLCHAR;
6607         bookRequested = FALSE;
6608         /* Program may be pondering now */
6609         cps->maybeThinking = TRUE;
6610         if (cps->sendTime == 2) cps->sendTime = 1;
6611         if (cps->offeredDraw) cps->offeredDraw--;
6612
6613         /* currentMoveString is set as a side-effect of ParseOneMove */
6614         strcpy(machineMove, currentMoveString);
6615         strcat(machineMove, "\n");
6616         strcpy(moveList[forwardMostMove], machineMove);
6617
6618         /* [AS] Save move info and clear stats for next move */
6619         pvInfoList[ forwardMostMove ].score = programStats.score;
6620         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6621         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6622         ClearProgramStats();
6623         thinkOutput[0] = NULLCHAR;
6624         hiddenThinkOutputState = 0;
6625
6626         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6627
6628         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6629         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6630             int count = 0;
6631
6632             while( count < adjudicateLossPlies ) {
6633                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6634
6635                 if( count & 1 ) {
6636                     score = -score; /* Flip score for winning side */
6637                 }
6638
6639                 if( score > adjudicateLossThreshold ) {
6640                     break;
6641                 }
6642
6643                 count++;
6644             }
6645
6646             if( count >= adjudicateLossPlies ) {
6647                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6648
6649                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6650                     "Xboard adjudication", 
6651                     GE_XBOARD );
6652
6653                 return;
6654             }
6655         }
6656
6657         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6658
6659 #if ZIPPY
6660         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6661             first.initDone) {
6662           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6663                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6664                 SendToICS("draw ");
6665                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6666           }
6667           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6668           ics_user_moved = 1;
6669           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6670                 char buf[3*MSG_SIZ];
6671
6672                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6673                         programStats.score / 100.,
6674                         programStats.depth,
6675                         programStats.time / 100.,
6676                         (unsigned int)programStats.nodes,
6677                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6678                         programStats.movelist);
6679                 SendToICS(buf);
6680 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6681           }
6682         }
6683 #endif
6684
6685         bookHit = NULL;
6686         if (gameMode == TwoMachinesPlay) {
6687             /* [HGM] relaying draw offers moved to after reception of move */
6688             /* and interpreting offer as claim if it brings draw condition */
6689             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6690                 SendToProgram("draw\n", cps->other);
6691             }
6692             if (cps->other->sendTime) {
6693                 SendTimeRemaining(cps->other,
6694                                   cps->other->twoMachinesColor[0] == 'w');
6695             }
6696             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6697             if (firstMove && !bookHit) {
6698                 firstMove = FALSE;
6699                 if (cps->other->useColors) {
6700                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6701                 }
6702                 SendToProgram("go\n", cps->other);
6703             }
6704             cps->other->maybeThinking = TRUE;
6705         }
6706
6707         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6708         
6709         if (!pausing && appData.ringBellAfterMoves) {
6710             RingBell();
6711         }
6712
6713         /* 
6714          * Reenable menu items that were disabled while
6715          * machine was thinking
6716          */
6717         if (gameMode != TwoMachinesPlay)
6718             SetUserThinkingEnables();
6719
6720         // [HGM] book: after book hit opponent has received move and is now in force mode
6721         // force the book reply into it, and then fake that it outputted this move by jumping
6722         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6723         if(bookHit) {
6724                 static char bookMove[MSG_SIZ]; // a bit generous?
6725
6726                 strcpy(bookMove, "move ");
6727                 strcat(bookMove, bookHit);
6728                 message = bookMove;
6729                 cps = cps->other;
6730                 programStats.nodes = programStats.depth = programStats.time = 
6731                 programStats.score = programStats.got_only_move = 0;
6732                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6733
6734                 if(cps->lastPing != cps->lastPong) {
6735                     savedMessage = message; // args for deferred call
6736                     savedState = cps;
6737                     ScheduleDelayedEvent(DeferredBookMove, 10);
6738                     return;
6739                 }
6740                 goto FakeBookMove;
6741         }
6742
6743         return;
6744     }
6745
6746     /* Set special modes for chess engines.  Later something general
6747      *  could be added here; for now there is just one kludge feature,
6748      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6749      *  when "xboard" is given as an interactive command.
6750      */
6751     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6752         cps->useSigint = FALSE;
6753         cps->useSigterm = FALSE;
6754     }
6755     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6756       ParseFeatures(message+8, cps);
6757       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6758     }
6759
6760     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6761      * want this, I was asked to put it in, and obliged.
6762      */
6763     if (!strncmp(message, "setboard ", 9)) {
6764         Board initial_position;
6765
6766         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6767
6768         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6769             DisplayError(_("Bad FEN received from engine"), 0);
6770             return ;
6771         } else {
6772            Reset(TRUE, FALSE);
6773            CopyBoard(boards[0], initial_position);
6774            initialRulePlies = FENrulePlies;
6775            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6776            else gameMode = MachinePlaysBlack;                 
6777            DrawPosition(FALSE, boards[currentMove]);
6778         }
6779         return;
6780     }
6781
6782     /*
6783      * Look for communication commands
6784      */
6785     if (!strncmp(message, "telluser ", 9)) {
6786         DisplayNote(message + 9);
6787         return;
6788     }
6789     if (!strncmp(message, "tellusererror ", 14)) {
6790         cps->userError = 1;
6791         DisplayError(message + 14, 0);
6792         return;
6793     }
6794     if (!strncmp(message, "tellopponent ", 13)) {
6795       if (appData.icsActive) {
6796         if (loggedOn) {
6797           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6798           SendToICS(buf1);
6799         }
6800       } else {
6801         DisplayNote(message + 13);
6802       }
6803       return;
6804     }
6805     if (!strncmp(message, "tellothers ", 11)) {
6806       if (appData.icsActive) {
6807         if (loggedOn) {
6808           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6809           SendToICS(buf1);
6810         }
6811       }
6812       return;
6813     }
6814     if (!strncmp(message, "tellall ", 8)) {
6815       if (appData.icsActive) {
6816         if (loggedOn) {
6817           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6818           SendToICS(buf1);
6819         }
6820       } else {
6821         DisplayNote(message + 8);
6822       }
6823       return;
6824     }
6825     if (strncmp(message, "warning", 7) == 0) {
6826         /* Undocumented feature, use tellusererror in new code */
6827         DisplayError(message, 0);
6828         return;
6829     }
6830     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6831         strcpy(realname, cps->tidy);
6832         strcat(realname, " query");
6833         AskQuestion(realname, buf2, buf1, cps->pr);
6834         return;
6835     }
6836     /* Commands from the engine directly to ICS.  We don't allow these to be 
6837      *  sent until we are logged on. Crafty kibitzes have been known to 
6838      *  interfere with the login process.
6839      */
6840     if (loggedOn) {
6841         if (!strncmp(message, "tellics ", 8)) {
6842             SendToICS(message + 8);
6843             SendToICS("\n");
6844             return;
6845         }
6846         if (!strncmp(message, "tellicsnoalias ", 15)) {
6847             SendToICS(ics_prefix);
6848             SendToICS(message + 15);
6849             SendToICS("\n");
6850             return;
6851         }
6852         /* The following are for backward compatibility only */
6853         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6854             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6855             SendToICS(ics_prefix);
6856             SendToICS(message);
6857             SendToICS("\n");
6858             return;
6859         }
6860     }
6861     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6862         return;
6863     }
6864     /*
6865      * If the move is illegal, cancel it and redraw the board.
6866      * Also deal with other error cases.  Matching is rather loose
6867      * here to accommodate engines written before the spec.
6868      */
6869     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6870         strncmp(message, "Error", 5) == 0) {
6871         if (StrStr(message, "name") || 
6872             StrStr(message, "rating") || StrStr(message, "?") ||
6873             StrStr(message, "result") || StrStr(message, "board") ||
6874             StrStr(message, "bk") || StrStr(message, "computer") ||
6875             StrStr(message, "variant") || StrStr(message, "hint") ||
6876             StrStr(message, "random") || StrStr(message, "depth") ||
6877             StrStr(message, "accepted")) {
6878             return;
6879         }
6880         if (StrStr(message, "protover")) {
6881           /* Program is responding to input, so it's apparently done
6882              initializing, and this error message indicates it is
6883              protocol version 1.  So we don't need to wait any longer
6884              for it to initialize and send feature commands. */
6885           FeatureDone(cps, 1);
6886           cps->protocolVersion = 1;
6887           return;
6888         }
6889         cps->maybeThinking = FALSE;
6890
6891         if (StrStr(message, "draw")) {
6892             /* Program doesn't have "draw" command */
6893             cps->sendDrawOffers = 0;
6894             return;
6895         }
6896         if (cps->sendTime != 1 &&
6897             (StrStr(message, "time") || StrStr(message, "otim"))) {
6898           /* Program apparently doesn't have "time" or "otim" command */
6899           cps->sendTime = 0;
6900           return;
6901         }
6902         if (StrStr(message, "analyze")) {
6903             cps->analysisSupport = FALSE;
6904             cps->analyzing = FALSE;
6905             Reset(FALSE, TRUE);
6906             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6907             DisplayError(buf2, 0);
6908             return;
6909         }
6910         if (StrStr(message, "(no matching move)st")) {
6911           /* Special kludge for GNU Chess 4 only */
6912           cps->stKludge = TRUE;
6913           SendTimeControl(cps, movesPerSession, timeControl,
6914                           timeIncrement, appData.searchDepth,
6915                           searchTime);
6916           return;
6917         }
6918         if (StrStr(message, "(no matching move)sd")) {
6919           /* Special kludge for GNU Chess 4 only */
6920           cps->sdKludge = TRUE;
6921           SendTimeControl(cps, movesPerSession, timeControl,
6922                           timeIncrement, appData.searchDepth,
6923                           searchTime);
6924           return;
6925         }
6926         if (!StrStr(message, "llegal")) {
6927             return;
6928         }
6929         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6930             gameMode == IcsIdle) return;
6931         if (forwardMostMove <= backwardMostMove) return;
6932         if (pausing) PauseEvent();
6933       if(appData.forceIllegal) {
6934             // [HGM] illegal: machine refused move; force position after move into it
6935           SendToProgram("force\n", cps);
6936           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6937                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6938                 // when black is to move, while there might be nothing on a2 or black
6939                 // might already have the move. So send the board as if white has the move.
6940                 // But first we must change the stm of the engine, as it refused the last move
6941                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6942                 if(WhiteOnMove(forwardMostMove)) {
6943                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6944                     SendBoard(cps, forwardMostMove); // kludgeless board
6945                 } else {
6946                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6947                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6948                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6949                 }
6950           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6951             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6952                  gameMode == TwoMachinesPlay)
6953               SendToProgram("go\n", cps);
6954             return;
6955       } else
6956         if (gameMode == PlayFromGameFile) {
6957             /* Stop reading this game file */
6958             gameMode = EditGame;
6959             ModeHighlight();
6960         }
6961         currentMove = --forwardMostMove;
6962         DisplayMove(currentMove-1); /* before DisplayMoveError */
6963         SwitchClocks();
6964         DisplayBothClocks();
6965         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6966                 parseList[currentMove], cps->which);
6967         DisplayMoveError(buf1);
6968         DrawPosition(FALSE, boards[currentMove]);
6969
6970         /* [HGM] illegal-move claim should forfeit game when Xboard */
6971         /* only passes fully legal moves                            */
6972         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6973             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6974                                 "False illegal-move claim", GE_XBOARD );
6975         }
6976         return;
6977     }
6978     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6979         /* Program has a broken "time" command that
6980            outputs a string not ending in newline.
6981            Don't use it. */
6982         cps->sendTime = 0;
6983     }
6984     
6985     /*
6986      * If chess program startup fails, exit with an error message.
6987      * Attempts to recover here are futile.
6988      */
6989     if ((StrStr(message, "unknown host") != NULL)
6990         || (StrStr(message, "No remote directory") != NULL)
6991         || (StrStr(message, "not found") != NULL)
6992         || (StrStr(message, "No such file") != NULL)
6993         || (StrStr(message, "can't alloc") != NULL)
6994         || (StrStr(message, "Permission denied") != NULL)) {
6995
6996         cps->maybeThinking = FALSE;
6997         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6998                 cps->which, cps->program, cps->host, message);
6999         RemoveInputSource(cps->isr);
7000         DisplayFatalError(buf1, 0, 1);
7001         return;
7002     }
7003     
7004     /* 
7005      * Look for hint output
7006      */
7007     if (sscanf(message, "Hint: %s", buf1) == 1) {
7008         if (cps == &first && hintRequested) {
7009             hintRequested = FALSE;
7010             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7011                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7012                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7013                                     PosFlags(forwardMostMove),
7014                                     fromY, fromX, toY, toX, promoChar, buf1);
7015                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7016                 DisplayInformation(buf2);
7017             } else {
7018                 /* Hint move could not be parsed!? */
7019               snprintf(buf2, sizeof(buf2),
7020                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7021                         buf1, cps->which);
7022                 DisplayError(buf2, 0);
7023             }
7024         } else {
7025             strcpy(lastHint, buf1);
7026         }
7027         return;
7028     }
7029
7030     /*
7031      * Ignore other messages if game is not in progress
7032      */
7033     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7034         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7035
7036     /*
7037      * look for win, lose, draw, or draw offer
7038      */
7039     if (strncmp(message, "1-0", 3) == 0) {
7040         char *p, *q, *r = "";
7041         p = strchr(message, '{');
7042         if (p) {
7043             q = strchr(p, '}');
7044             if (q) {
7045                 *q = NULLCHAR;
7046                 r = p + 1;
7047             }
7048         }
7049         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7050         return;
7051     } else if (strncmp(message, "0-1", 3) == 0) {
7052         char *p, *q, *r = "";
7053         p = strchr(message, '{');
7054         if (p) {
7055             q = strchr(p, '}');
7056             if (q) {
7057                 *q = NULLCHAR;
7058                 r = p + 1;
7059             }
7060         }
7061         /* Kludge for Arasan 4.1 bug */
7062         if (strcmp(r, "Black resigns") == 0) {
7063             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7064             return;
7065         }
7066         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7067         return;
7068     } else if (strncmp(message, "1/2", 3) == 0) {
7069         char *p, *q, *r = "";
7070         p = strchr(message, '{');
7071         if (p) {
7072             q = strchr(p, '}');
7073             if (q) {
7074                 *q = NULLCHAR;
7075                 r = p + 1;
7076             }
7077         }
7078             
7079         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7080         return;
7081
7082     } else if (strncmp(message, "White resign", 12) == 0) {
7083         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7084         return;
7085     } else if (strncmp(message, "Black resign", 12) == 0) {
7086         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7087         return;
7088     } else if (strncmp(message, "White matches", 13) == 0 ||
7089                strncmp(message, "Black matches", 13) == 0   ) {
7090         /* [HGM] ignore GNUShogi noises */
7091         return;
7092     } else if (strncmp(message, "White", 5) == 0 &&
7093                message[5] != '(' &&
7094                StrStr(message, "Black") == NULL) {
7095         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7096         return;
7097     } else if (strncmp(message, "Black", 5) == 0 &&
7098                message[5] != '(') {
7099         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7100         return;
7101     } else if (strcmp(message, "resign") == 0 ||
7102                strcmp(message, "computer resigns") == 0) {
7103         switch (gameMode) {
7104           case MachinePlaysBlack:
7105           case IcsPlayingBlack:
7106             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7107             break;
7108           case MachinePlaysWhite:
7109           case IcsPlayingWhite:
7110             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7111             break;
7112           case TwoMachinesPlay:
7113             if (cps->twoMachinesColor[0] == 'w')
7114               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7115             else
7116               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7117             break;
7118           default:
7119             /* can't happen */
7120             break;
7121         }
7122         return;
7123     } else if (strncmp(message, "opponent mates", 14) == 0) {
7124         switch (gameMode) {
7125           case MachinePlaysBlack:
7126           case IcsPlayingBlack:
7127             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7128             break;
7129           case MachinePlaysWhite:
7130           case IcsPlayingWhite:
7131             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7132             break;
7133           case TwoMachinesPlay:
7134             if (cps->twoMachinesColor[0] == 'w')
7135               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7136             else
7137               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7138             break;
7139           default:
7140             /* can't happen */
7141             break;
7142         }
7143         return;
7144     } else if (strncmp(message, "computer mates", 14) == 0) {
7145         switch (gameMode) {
7146           case MachinePlaysBlack:
7147           case IcsPlayingBlack:
7148             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7149             break;
7150           case MachinePlaysWhite:
7151           case IcsPlayingWhite:
7152             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7153             break;
7154           case TwoMachinesPlay:
7155             if (cps->twoMachinesColor[0] == 'w')
7156               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7157             else
7158               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7159             break;
7160           default:
7161             /* can't happen */
7162             break;
7163         }
7164         return;
7165     } else if (strncmp(message, "checkmate", 9) == 0) {
7166         if (WhiteOnMove(forwardMostMove)) {
7167             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7168         } else {
7169             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7170         }
7171         return;
7172     } else if (strstr(message, "Draw") != NULL ||
7173                strstr(message, "game is a draw") != NULL) {
7174         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7175         return;
7176     } else if (strstr(message, "offer") != NULL &&
7177                strstr(message, "draw") != NULL) {
7178 #if ZIPPY
7179         if (appData.zippyPlay && first.initDone) {
7180             /* Relay offer to ICS */
7181             SendToICS(ics_prefix);
7182             SendToICS("draw\n");
7183         }
7184 #endif
7185         cps->offeredDraw = 2; /* valid until this engine moves twice */
7186         if (gameMode == TwoMachinesPlay) {
7187             if (cps->other->offeredDraw) {
7188                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7189             /* [HGM] in two-machine mode we delay relaying draw offer      */
7190             /* until after we also have move, to see if it is really claim */
7191             }
7192         } else if (gameMode == MachinePlaysWhite ||
7193                    gameMode == MachinePlaysBlack) {
7194           if (userOfferedDraw) {
7195             DisplayInformation(_("Machine accepts your draw offer"));
7196             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7197           } else {
7198             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7199           }
7200         }
7201     }
7202
7203     
7204     /*
7205      * Look for thinking output
7206      */
7207     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7208           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7209                                 ) {
7210         int plylev, mvleft, mvtot, curscore, time;
7211         char mvname[MOVE_LEN];
7212         u64 nodes; // [DM]
7213         char plyext;
7214         int ignore = FALSE;
7215         int prefixHint = FALSE;
7216         mvname[0] = NULLCHAR;
7217
7218         switch (gameMode) {
7219           case MachinePlaysBlack:
7220           case IcsPlayingBlack:
7221             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7222             break;
7223           case MachinePlaysWhite:
7224           case IcsPlayingWhite:
7225             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7226             break;
7227           case AnalyzeMode:
7228           case AnalyzeFile:
7229             break;
7230           case IcsObserving: /* [DM] icsEngineAnalyze */
7231             if (!appData.icsEngineAnalyze) ignore = TRUE;
7232             break;
7233           case TwoMachinesPlay:
7234             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7235                 ignore = TRUE;
7236             }
7237             break;
7238           default:
7239             ignore = TRUE;
7240             break;
7241         }
7242
7243         if (!ignore) {
7244             buf1[0] = NULLCHAR;
7245             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7246                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7247
7248                 if (plyext != ' ' && plyext != '\t') {
7249                     time *= 100;
7250                 }
7251
7252                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7253                 if( cps->scoreIsAbsolute && 
7254                     ( gameMode == MachinePlaysBlack ||
7255                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7256                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7257                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7258                      !WhiteOnMove(currentMove)
7259                     ) )
7260                 {
7261                     curscore = -curscore;
7262                 }
7263
7264
7265                 programStats.depth = plylev;
7266                 programStats.nodes = nodes;
7267                 programStats.time = time;
7268                 programStats.score = curscore;
7269                 programStats.got_only_move = 0;
7270
7271                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7272                         int ticklen;
7273
7274                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7275                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7276                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7277                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7278                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7279                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7280                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7281                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7282                 }
7283
7284                 /* Buffer overflow protection */
7285                 if (buf1[0] != NULLCHAR) {
7286                     if (strlen(buf1) >= sizeof(programStats.movelist)
7287                         && appData.debugMode) {
7288                         fprintf(debugFP,
7289                                 "PV is too long; using the first %u bytes.\n",
7290                                 (unsigned) sizeof(programStats.movelist) - 1);
7291                     }
7292
7293                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7294                 } else {
7295                     sprintf(programStats.movelist, " no PV\n");
7296                 }
7297
7298                 if (programStats.seen_stat) {
7299                     programStats.ok_to_send = 1;
7300                 }
7301
7302                 if (strchr(programStats.movelist, '(') != NULL) {
7303                     programStats.line_is_book = 1;
7304                     programStats.nr_moves = 0;
7305                     programStats.moves_left = 0;
7306                 } else {
7307                     programStats.line_is_book = 0;
7308                 }
7309
7310                 SendProgramStatsToFrontend( cps, &programStats );
7311
7312                 /* 
7313                     [AS] Protect the thinkOutput buffer from overflow... this
7314                     is only useful if buf1 hasn't overflowed first!
7315                 */
7316                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7317                         plylev, 
7318                         (gameMode == TwoMachinesPlay ?
7319                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7320                         ((double) curscore) / 100.0,
7321                         prefixHint ? lastHint : "",
7322                         prefixHint ? " " : "" );
7323
7324                 if( buf1[0] != NULLCHAR ) {
7325                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7326
7327                     if( strlen(buf1) > max_len ) {
7328                         if( appData.debugMode) {
7329                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7330                         }
7331                         buf1[max_len+1] = '\0';
7332                     }
7333
7334                     strcat( thinkOutput, buf1 );
7335                 }
7336
7337                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7338                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7339                     DisplayMove(currentMove - 1);
7340                 }
7341                 return;
7342
7343             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7344                 /* crafty (9.25+) says "(only move) <move>"
7345                  * if there is only 1 legal move
7346                  */
7347                 sscanf(p, "(only move) %s", buf1);
7348                 sprintf(thinkOutput, "%s (only move)", buf1);
7349                 sprintf(programStats.movelist, "%s (only move)", buf1);
7350                 programStats.depth = 1;
7351                 programStats.nr_moves = 1;
7352                 programStats.moves_left = 1;
7353                 programStats.nodes = 1;
7354                 programStats.time = 1;
7355                 programStats.got_only_move = 1;
7356
7357                 /* Not really, but we also use this member to
7358                    mean "line isn't going to change" (Crafty
7359                    isn't searching, so stats won't change) */
7360                 programStats.line_is_book = 1;
7361
7362                 SendProgramStatsToFrontend( cps, &programStats );
7363                 
7364                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7365                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7366                     DisplayMove(currentMove - 1);
7367                 }
7368                 return;
7369             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7370                               &time, &nodes, &plylev, &mvleft,
7371                               &mvtot, mvname) >= 5) {
7372                 /* The stat01: line is from Crafty (9.29+) in response
7373                    to the "." command */
7374                 programStats.seen_stat = 1;
7375                 cps->maybeThinking = TRUE;
7376
7377                 if (programStats.got_only_move || !appData.periodicUpdates)
7378                   return;
7379
7380                 programStats.depth = plylev;
7381                 programStats.time = time;
7382                 programStats.nodes = nodes;
7383                 programStats.moves_left = mvleft;
7384                 programStats.nr_moves = mvtot;
7385                 strcpy(programStats.move_name, mvname);
7386                 programStats.ok_to_send = 1;
7387                 programStats.movelist[0] = '\0';
7388
7389                 SendProgramStatsToFrontend( cps, &programStats );
7390
7391                 return;
7392
7393             } else if (strncmp(message,"++",2) == 0) {
7394                 /* Crafty 9.29+ outputs this */
7395                 programStats.got_fail = 2;
7396                 return;
7397
7398             } else if (strncmp(message,"--",2) == 0) {
7399                 /* Crafty 9.29+ outputs this */
7400                 programStats.got_fail = 1;
7401                 return;
7402
7403             } else if (thinkOutput[0] != NULLCHAR &&
7404                        strncmp(message, "    ", 4) == 0) {
7405                 unsigned message_len;
7406
7407                 p = message;
7408                 while (*p && *p == ' ') p++;
7409
7410                 message_len = strlen( p );
7411
7412                 /* [AS] Avoid buffer overflow */
7413                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7414                     strcat(thinkOutput, " ");
7415                     strcat(thinkOutput, p);
7416                 }
7417
7418                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7419                     strcat(programStats.movelist, " ");
7420                     strcat(programStats.movelist, p);
7421                 }
7422
7423                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7424                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7425                     DisplayMove(currentMove - 1);
7426                 }
7427                 return;
7428             }
7429         }
7430         else {
7431             buf1[0] = NULLCHAR;
7432
7433             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7434                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7435             {
7436                 ChessProgramStats cpstats;
7437
7438                 if (plyext != ' ' && plyext != '\t') {
7439                     time *= 100;
7440                 }
7441
7442                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7443                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7444                     curscore = -curscore;
7445                 }
7446
7447                 cpstats.depth = plylev;
7448                 cpstats.nodes = nodes;
7449                 cpstats.time = time;
7450                 cpstats.score = curscore;
7451                 cpstats.got_only_move = 0;
7452                 cpstats.movelist[0] = '\0';
7453
7454                 if (buf1[0] != NULLCHAR) {
7455                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7456                 }
7457
7458                 cpstats.ok_to_send = 0;
7459                 cpstats.line_is_book = 0;
7460                 cpstats.nr_moves = 0;
7461                 cpstats.moves_left = 0;
7462
7463                 SendProgramStatsToFrontend( cps, &cpstats );
7464             }
7465         }
7466     }
7467 }
7468
7469
7470 /* Parse a game score from the character string "game", and
7471    record it as the history of the current game.  The game
7472    score is NOT assumed to start from the standard position. 
7473    The display is not updated in any way.
7474    */
7475 void
7476 ParseGameHistory(game)
7477      char *game;
7478 {
7479     ChessMove moveType;
7480     int fromX, fromY, toX, toY, boardIndex;
7481     char promoChar;
7482     char *p, *q;
7483     char buf[MSG_SIZ];
7484
7485     if (appData.debugMode)
7486       fprintf(debugFP, "Parsing game history: %s\n", game);
7487
7488     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7489     gameInfo.site = StrSave(appData.icsHost);
7490     gameInfo.date = PGNDate();
7491     gameInfo.round = StrSave("-");
7492
7493     /* Parse out names of players */
7494     while (*game == ' ') game++;
7495     p = buf;
7496     while (*game != ' ') *p++ = *game++;
7497     *p = NULLCHAR;
7498     gameInfo.white = StrSave(buf);
7499     while (*game == ' ') game++;
7500     p = buf;
7501     while (*game != ' ' && *game != '\n') *p++ = *game++;
7502     *p = NULLCHAR;
7503     gameInfo.black = StrSave(buf);
7504
7505     /* Parse moves */
7506     boardIndex = blackPlaysFirst ? 1 : 0;
7507     yynewstr(game);
7508     for (;;) {
7509         yyboardindex = boardIndex;
7510         moveType = (ChessMove) yylex();
7511         switch (moveType) {
7512           case IllegalMove:             /* maybe suicide chess, etc. */
7513   if (appData.debugMode) {
7514     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7515     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7516     setbuf(debugFP, NULL);
7517   }
7518           case WhitePromotionChancellor:
7519           case BlackPromotionChancellor:
7520           case WhitePromotionArchbishop:
7521           case BlackPromotionArchbishop:
7522           case WhitePromotionQueen:
7523           case BlackPromotionQueen:
7524           case WhitePromotionRook:
7525           case BlackPromotionRook:
7526           case WhitePromotionBishop:
7527           case BlackPromotionBishop:
7528           case WhitePromotionKnight:
7529           case BlackPromotionKnight:
7530           case WhitePromotionKing:
7531           case BlackPromotionKing:
7532           case NormalMove:
7533           case WhiteCapturesEnPassant:
7534           case BlackCapturesEnPassant:
7535           case WhiteKingSideCastle:
7536           case WhiteQueenSideCastle:
7537           case BlackKingSideCastle:
7538           case BlackQueenSideCastle:
7539           case WhiteKingSideCastleWild:
7540           case WhiteQueenSideCastleWild:
7541           case BlackKingSideCastleWild:
7542           case BlackQueenSideCastleWild:
7543           /* PUSH Fabien */
7544           case WhiteHSideCastleFR:
7545           case WhiteASideCastleFR:
7546           case BlackHSideCastleFR:
7547           case BlackASideCastleFR:
7548           /* POP Fabien */
7549             fromX = currentMoveString[0] - AAA;
7550             fromY = currentMoveString[1] - ONE;
7551             toX = currentMoveString[2] - AAA;
7552             toY = currentMoveString[3] - ONE;
7553             promoChar = currentMoveString[4];
7554             break;
7555           case WhiteDrop:
7556           case BlackDrop:
7557             fromX = moveType == WhiteDrop ?
7558               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7559             (int) CharToPiece(ToLower(currentMoveString[0]));
7560             fromY = DROP_RANK;
7561             toX = currentMoveString[2] - AAA;
7562             toY = currentMoveString[3] - ONE;
7563             promoChar = NULLCHAR;
7564             break;
7565           case AmbiguousMove:
7566             /* bug? */
7567             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7568   if (appData.debugMode) {
7569     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7570     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7571     setbuf(debugFP, NULL);
7572   }
7573             DisplayError(buf, 0);
7574             return;
7575           case ImpossibleMove:
7576             /* bug? */
7577             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7578   if (appData.debugMode) {
7579     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7580     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7581     setbuf(debugFP, NULL);
7582   }
7583             DisplayError(buf, 0);
7584             return;
7585           case (ChessMove) 0:   /* end of file */
7586             if (boardIndex < backwardMostMove) {
7587                 /* Oops, gap.  How did that happen? */
7588                 DisplayError(_("Gap in move list"), 0);
7589                 return;
7590             }
7591             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7592             if (boardIndex > forwardMostMove) {
7593                 forwardMostMove = boardIndex;
7594             }
7595             return;
7596           case ElapsedTime:
7597             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7598                 strcat(parseList[boardIndex-1], " ");
7599                 strcat(parseList[boardIndex-1], yy_text);
7600             }
7601             continue;
7602           case Comment:
7603           case PGNTag:
7604           case NAG:
7605           default:
7606             /* ignore */
7607             continue;
7608           case WhiteWins:
7609           case BlackWins:
7610           case GameIsDrawn:
7611           case GameUnfinished:
7612             if (gameMode == IcsExamining) {
7613                 if (boardIndex < backwardMostMove) {
7614                     /* Oops, gap.  How did that happen? */
7615                     return;
7616                 }
7617                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7618                 return;
7619             }
7620             gameInfo.result = moveType;
7621             p = strchr(yy_text, '{');
7622             if (p == NULL) p = strchr(yy_text, '(');
7623             if (p == NULL) {
7624                 p = yy_text;
7625                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7626             } else {
7627                 q = strchr(p, *p == '{' ? '}' : ')');
7628                 if (q != NULL) *q = NULLCHAR;
7629                 p++;
7630             }
7631             gameInfo.resultDetails = StrSave(p);
7632             continue;
7633         }
7634         if (boardIndex >= forwardMostMove &&
7635             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7636             backwardMostMove = blackPlaysFirst ? 1 : 0;
7637             return;
7638         }
7639         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7640                                  fromY, fromX, toY, toX, promoChar,
7641                                  parseList[boardIndex]);
7642         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7643         /* currentMoveString is set as a side-effect of yylex */
7644         strcpy(moveList[boardIndex], currentMoveString);
7645         strcat(moveList[boardIndex], "\n");
7646         boardIndex++;
7647         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7648         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7649           case MT_NONE:
7650           case MT_STALEMATE:
7651           default:
7652             break;
7653           case MT_CHECK:
7654             if(gameInfo.variant != VariantShogi)
7655                 strcat(parseList[boardIndex - 1], "+");
7656             break;
7657           case MT_CHECKMATE:
7658           case MT_STAINMATE:
7659             strcat(parseList[boardIndex - 1], "#");
7660             break;
7661         }
7662     }
7663 }
7664
7665
7666 /* Apply a move to the given board  */
7667 void
7668 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7669      int fromX, fromY, toX, toY;
7670      int promoChar;
7671      Board board;
7672 {
7673   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7674   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7675
7676     /* [HGM] compute & store e.p. status and castling rights for new position */
7677     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7678     { int i;
7679
7680       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7681       oldEP = (signed char)board[EP_STATUS];
7682       board[EP_STATUS] = EP_NONE;
7683
7684       if( board[toY][toX] != EmptySquare ) 
7685            board[EP_STATUS] = EP_CAPTURE;  
7686
7687       if( board[fromY][fromX] == WhitePawn ) {
7688            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7689                board[EP_STATUS] = EP_PAWN_MOVE;
7690            if( toY-fromY==2) {
7691                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7692                         gameInfo.variant != VariantBerolina || toX < fromX)
7693                       board[EP_STATUS] = toX | berolina;
7694                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7695                         gameInfo.variant != VariantBerolina || toX > fromX) 
7696                       board[EP_STATUS] = toX;
7697            }
7698       } else 
7699       if( board[fromY][fromX] == BlackPawn ) {
7700            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7701                board[EP_STATUS] = EP_PAWN_MOVE; 
7702            if( toY-fromY== -2) {
7703                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7704                         gameInfo.variant != VariantBerolina || toX < fromX)
7705                       board[EP_STATUS] = toX | berolina;
7706                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7707                         gameInfo.variant != VariantBerolina || toX > fromX) 
7708                       board[EP_STATUS] = toX;
7709            }
7710        }
7711
7712        for(i=0; i<nrCastlingRights; i++) {
7713            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7714               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7715              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7716        }
7717
7718     }
7719
7720   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7721   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7722        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7723          
7724   if (fromX == toX && fromY == toY) return;
7725
7726   if (fromY == DROP_RANK) {
7727         /* must be first */
7728         piece = board[toY][toX] = (ChessSquare) fromX;
7729   } else {
7730      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7731      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7732      if(gameInfo.variant == VariantKnightmate)
7733          king += (int) WhiteUnicorn - (int) WhiteKing;
7734
7735     /* Code added by Tord: */
7736     /* FRC castling assumed when king captures friendly rook. */
7737     if (board[fromY][fromX] == WhiteKing &&
7738              board[toY][toX] == WhiteRook) {
7739       board[fromY][fromX] = EmptySquare;
7740       board[toY][toX] = EmptySquare;
7741       if(toX > fromX) {
7742         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7743       } else {
7744         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7745       }
7746     } else if (board[fromY][fromX] == BlackKing &&
7747                board[toY][toX] == BlackRook) {
7748       board[fromY][fromX] = EmptySquare;
7749       board[toY][toX] = EmptySquare;
7750       if(toX > fromX) {
7751         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7752       } else {
7753         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7754       }
7755     /* End of code added by Tord */
7756
7757     } else if (board[fromY][fromX] == king
7758         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7759         && toY == fromY && toX > fromX+1) {
7760         board[fromY][fromX] = EmptySquare;
7761         board[toY][toX] = king;
7762         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7763         board[fromY][BOARD_RGHT-1] = EmptySquare;
7764     } else if (board[fromY][fromX] == king
7765         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7766                && toY == fromY && toX < fromX-1) {
7767         board[fromY][fromX] = EmptySquare;
7768         board[toY][toX] = king;
7769         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7770         board[fromY][BOARD_LEFT] = EmptySquare;
7771     } else if (board[fromY][fromX] == WhitePawn
7772                && toY >= BOARD_HEIGHT-promoRank
7773                && gameInfo.variant != VariantXiangqi
7774                ) {
7775         /* white pawn promotion */
7776         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7777         if (board[toY][toX] == EmptySquare) {
7778             board[toY][toX] = WhiteQueen;
7779         }
7780         if(gameInfo.variant==VariantBughouse ||
7781            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7782             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7783         board[fromY][fromX] = EmptySquare;
7784     } else if ((fromY == BOARD_HEIGHT-4)
7785                && (toX != fromX)
7786                && gameInfo.variant != VariantXiangqi
7787                && gameInfo.variant != VariantBerolina
7788                && (board[fromY][fromX] == WhitePawn)
7789                && (board[toY][toX] == EmptySquare)) {
7790         board[fromY][fromX] = EmptySquare;
7791         board[toY][toX] = WhitePawn;
7792         captured = board[toY - 1][toX];
7793         board[toY - 1][toX] = EmptySquare;
7794     } else if ((fromY == BOARD_HEIGHT-4)
7795                && (toX == fromX)
7796                && gameInfo.variant == VariantBerolina
7797                && (board[fromY][fromX] == WhitePawn)
7798                && (board[toY][toX] == EmptySquare)) {
7799         board[fromY][fromX] = EmptySquare;
7800         board[toY][toX] = WhitePawn;
7801         if(oldEP & EP_BEROLIN_A) {
7802                 captured = board[fromY][fromX-1];
7803                 board[fromY][fromX-1] = EmptySquare;
7804         }else{  captured = board[fromY][fromX+1];
7805                 board[fromY][fromX+1] = EmptySquare;
7806         }
7807     } else if (board[fromY][fromX] == king
7808         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7809                && toY == fromY && toX > fromX+1) {
7810         board[fromY][fromX] = EmptySquare;
7811         board[toY][toX] = king;
7812         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7813         board[fromY][BOARD_RGHT-1] = EmptySquare;
7814     } else if (board[fromY][fromX] == king
7815         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7816                && toY == fromY && toX < fromX-1) {
7817         board[fromY][fromX] = EmptySquare;
7818         board[toY][toX] = king;
7819         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7820         board[fromY][BOARD_LEFT] = EmptySquare;
7821     } else if (fromY == 7 && fromX == 3
7822                && board[fromY][fromX] == BlackKing
7823                && toY == 7 && toX == 5) {
7824         board[fromY][fromX] = EmptySquare;
7825         board[toY][toX] = BlackKing;
7826         board[fromY][7] = EmptySquare;
7827         board[toY][4] = BlackRook;
7828     } else if (fromY == 7 && fromX == 3
7829                && board[fromY][fromX] == BlackKing
7830                && toY == 7 && toX == 1) {
7831         board[fromY][fromX] = EmptySquare;
7832         board[toY][toX] = BlackKing;
7833         board[fromY][0] = EmptySquare;
7834         board[toY][2] = BlackRook;
7835     } else if (board[fromY][fromX] == BlackPawn
7836                && toY < promoRank
7837                && gameInfo.variant != VariantXiangqi
7838                ) {
7839         /* black pawn promotion */
7840         board[toY][toX] = CharToPiece(ToLower(promoChar));
7841         if (board[toY][toX] == EmptySquare) {
7842             board[toY][toX] = BlackQueen;
7843         }
7844         if(gameInfo.variant==VariantBughouse ||
7845            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7846             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7847         board[fromY][fromX] = EmptySquare;
7848     } else if ((fromY == 3)
7849                && (toX != fromX)
7850                && gameInfo.variant != VariantXiangqi
7851                && gameInfo.variant != VariantBerolina
7852                && (board[fromY][fromX] == BlackPawn)
7853                && (board[toY][toX] == EmptySquare)) {
7854         board[fromY][fromX] = EmptySquare;
7855         board[toY][toX] = BlackPawn;
7856         captured = board[toY + 1][toX];
7857         board[toY + 1][toX] = EmptySquare;
7858     } else if ((fromY == 3)
7859                && (toX == fromX)
7860                && gameInfo.variant == VariantBerolina
7861                && (board[fromY][fromX] == BlackPawn)
7862                && (board[toY][toX] == EmptySquare)) {
7863         board[fromY][fromX] = EmptySquare;
7864         board[toY][toX] = BlackPawn;
7865         if(oldEP & EP_BEROLIN_A) {
7866                 captured = board[fromY][fromX-1];
7867                 board[fromY][fromX-1] = EmptySquare;
7868         }else{  captured = board[fromY][fromX+1];
7869                 board[fromY][fromX+1] = EmptySquare;
7870         }
7871     } else {
7872         board[toY][toX] = board[fromY][fromX];
7873         board[fromY][fromX] = EmptySquare;
7874     }
7875
7876     /* [HGM] now we promote for Shogi, if needed */
7877     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7878         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7879   }
7880
7881     if (gameInfo.holdingsWidth != 0) {
7882
7883       /* !!A lot more code needs to be written to support holdings  */
7884       /* [HGM] OK, so I have written it. Holdings are stored in the */
7885       /* penultimate board files, so they are automaticlly stored   */
7886       /* in the game history.                                       */
7887       if (fromY == DROP_RANK) {
7888         /* Delete from holdings, by decreasing count */
7889         /* and erasing image if necessary            */
7890         p = (int) fromX;
7891         if(p < (int) BlackPawn) { /* white drop */
7892              p -= (int)WhitePawn;
7893                  p = PieceToNumber((ChessSquare)p);
7894              if(p >= gameInfo.holdingsSize) p = 0;
7895              if(--board[p][BOARD_WIDTH-2] <= 0)
7896                   board[p][BOARD_WIDTH-1] = EmptySquare;
7897              if((int)board[p][BOARD_WIDTH-2] < 0)
7898                         board[p][BOARD_WIDTH-2] = 0;
7899         } else {                  /* black drop */
7900              p -= (int)BlackPawn;
7901                  p = PieceToNumber((ChessSquare)p);
7902              if(p >= gameInfo.holdingsSize) p = 0;
7903              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7904                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7905              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7906                         board[BOARD_HEIGHT-1-p][1] = 0;
7907         }
7908       }
7909       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7910           && gameInfo.variant != VariantBughouse        ) {
7911         /* [HGM] holdings: Add to holdings, if holdings exist */
7912         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7913                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7914                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7915         }
7916         p = (int) captured;
7917         if (p >= (int) BlackPawn) {
7918           p -= (int)BlackPawn;
7919           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7920                   /* in Shogi restore piece to its original  first */
7921                   captured = (ChessSquare) (DEMOTED captured);
7922                   p = DEMOTED p;
7923           }
7924           p = PieceToNumber((ChessSquare)p);
7925           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7926           board[p][BOARD_WIDTH-2]++;
7927           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7928         } else {
7929           p -= (int)WhitePawn;
7930           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7931                   captured = (ChessSquare) (DEMOTED captured);
7932                   p = DEMOTED p;
7933           }
7934           p = PieceToNumber((ChessSquare)p);
7935           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7936           board[BOARD_HEIGHT-1-p][1]++;
7937           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7938         }
7939       }
7940     } else if (gameInfo.variant == VariantAtomic) {
7941       if (captured != EmptySquare) {
7942         int y, x;
7943         for (y = toY-1; y <= toY+1; y++) {
7944           for (x = toX-1; x <= toX+1; x++) {
7945             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7946                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7947               board[y][x] = EmptySquare;
7948             }
7949           }
7950         }
7951         board[toY][toX] = EmptySquare;
7952       }
7953     }
7954     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7955         /* [HGM] Shogi promotions */
7956         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7957     }
7958
7959     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7960                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7961         // [HGM] superchess: take promotion piece out of holdings
7962         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7963         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7964             if(!--board[k][BOARD_WIDTH-2])
7965                 board[k][BOARD_WIDTH-1] = EmptySquare;
7966         } else {
7967             if(!--board[BOARD_HEIGHT-1-k][1])
7968                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7969         }
7970     }
7971
7972 }
7973
7974 /* Updates forwardMostMove */
7975 void
7976 MakeMove(fromX, fromY, toX, toY, promoChar)
7977      int fromX, fromY, toX, toY;
7978      int promoChar;
7979 {
7980 //    forwardMostMove++; // [HGM] bare: moved downstream
7981
7982     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7983         int timeLeft; static int lastLoadFlag=0; int king, piece;
7984         piece = boards[forwardMostMove][fromY][fromX];
7985         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7986         if(gameInfo.variant == VariantKnightmate)
7987             king += (int) WhiteUnicorn - (int) WhiteKing;
7988         if(forwardMostMove == 0) {
7989             if(blackPlaysFirst) 
7990                 fprintf(serverMoves, "%s;", second.tidy);
7991             fprintf(serverMoves, "%s;", first.tidy);
7992             if(!blackPlaysFirst) 
7993                 fprintf(serverMoves, "%s;", second.tidy);
7994         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7995         lastLoadFlag = loadFlag;
7996         // print base move
7997         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7998         // print castling suffix
7999         if( toY == fromY && piece == king ) {
8000             if(toX-fromX > 1)
8001                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8002             if(fromX-toX >1)
8003                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8004         }
8005         // e.p. suffix
8006         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8007              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8008              boards[forwardMostMove][toY][toX] == EmptySquare
8009              && fromX != toX )
8010                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8011         // promotion suffix
8012         if(promoChar != NULLCHAR)
8013                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8014         if(!loadFlag) {
8015             fprintf(serverMoves, "/%d/%d",
8016                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8017             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8018             else                      timeLeft = blackTimeRemaining/1000;
8019             fprintf(serverMoves, "/%d", timeLeft);
8020         }
8021         fflush(serverMoves);
8022     }
8023
8024     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8025       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8026                         0, 1);
8027       return;
8028     }
8029     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8030     if (commentList[forwardMostMove+1] != NULL) {
8031         free(commentList[forwardMostMove+1]);
8032         commentList[forwardMostMove+1] = NULL;
8033     }
8034     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8035     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8036     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8037     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8038     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8039     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8040     gameInfo.result = GameUnfinished;
8041     if (gameInfo.resultDetails != NULL) {
8042         free(gameInfo.resultDetails);
8043         gameInfo.resultDetails = NULL;
8044     }
8045     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8046                               moveList[forwardMostMove - 1]);
8047     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8048                              PosFlags(forwardMostMove - 1),
8049                              fromY, fromX, toY, toX, promoChar,
8050                              parseList[forwardMostMove - 1]);
8051     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8052       case MT_NONE:
8053       case MT_STALEMATE:
8054       default:
8055         break;
8056       case MT_CHECK:
8057         if(gameInfo.variant != VariantShogi)
8058             strcat(parseList[forwardMostMove - 1], "+");
8059         break;
8060       case MT_CHECKMATE:
8061       case MT_STAINMATE:
8062         strcat(parseList[forwardMostMove - 1], "#");
8063         break;
8064     }
8065     if (appData.debugMode) {
8066         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8067     }
8068
8069 }
8070
8071 /* Updates currentMove if not pausing */
8072 void
8073 ShowMove(fromX, fromY, toX, toY)
8074 {
8075     int instant = (gameMode == PlayFromGameFile) ?
8076         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8077     if(appData.noGUI) return;
8078     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8079         if (!instant) {
8080             if (forwardMostMove == currentMove + 1) {
8081                 AnimateMove(boards[forwardMostMove - 1],
8082                             fromX, fromY, toX, toY);
8083             }
8084             if (appData.highlightLastMove) {
8085                 SetHighlights(fromX, fromY, toX, toY);
8086             }
8087         }
8088         currentMove = forwardMostMove;
8089     }
8090
8091     if (instant) return;
8092
8093     DisplayMove(currentMove - 1);
8094     DrawPosition(FALSE, boards[currentMove]);
8095     DisplayBothClocks();
8096     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8097 }
8098
8099 void SendEgtPath(ChessProgramState *cps)
8100 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8101         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8102
8103         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8104
8105         while(*p) {
8106             char c, *q = name+1, *r, *s;
8107
8108             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8109             while(*p && *p != ',') *q++ = *p++;
8110             *q++ = ':'; *q = 0;
8111             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8112                 strcmp(name, ",nalimov:") == 0 ) {
8113                 // take nalimov path from the menu-changeable option first, if it is defined
8114                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8115                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8116             } else
8117             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8118                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8119                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8120                 s = r = StrStr(s, ":") + 1; // beginning of path info
8121                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8122                 c = *r; *r = 0;             // temporarily null-terminate path info
8123                     *--q = 0;               // strip of trailig ':' from name
8124                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8125                 *r = c;
8126                 SendToProgram(buf,cps);     // send egtbpath command for this format
8127             }
8128             if(*p == ',') p++; // read away comma to position for next format name
8129         }
8130 }
8131
8132 void
8133 InitChessProgram(cps, setup)
8134      ChessProgramState *cps;
8135      int setup; /* [HGM] needed to setup FRC opening position */
8136 {
8137     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8138     if (appData.noChessProgram) return;
8139     hintRequested = FALSE;
8140     bookRequested = FALSE;
8141
8142     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8143     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8144     if(cps->memSize) { /* [HGM] memory */
8145         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8146         SendToProgram(buf, cps);
8147     }
8148     SendEgtPath(cps); /* [HGM] EGT */
8149     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8150         sprintf(buf, "cores %d\n", appData.smpCores);
8151         SendToProgram(buf, cps);
8152     }
8153
8154     SendToProgram(cps->initString, cps);
8155     if (gameInfo.variant != VariantNormal &&
8156         gameInfo.variant != VariantLoadable
8157         /* [HGM] also send variant if board size non-standard */
8158         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8159                                             ) {
8160       char *v = VariantName(gameInfo.variant);
8161       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8162         /* [HGM] in protocol 1 we have to assume all variants valid */
8163         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8164         DisplayFatalError(buf, 0, 1);
8165         return;
8166       }
8167
8168       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8169       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8170       if( gameInfo.variant == VariantXiangqi )
8171            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8172       if( gameInfo.variant == VariantShogi )
8173            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8174       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8175            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8176       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8177                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8178            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8179       if( gameInfo.variant == VariantCourier )
8180            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8181       if( gameInfo.variant == VariantSuper )
8182            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8183       if( gameInfo.variant == VariantGreat )
8184            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8185
8186       if(overruled) {
8187            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8188                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8189            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8190            if(StrStr(cps->variants, b) == NULL) { 
8191                // specific sized variant not known, check if general sizing allowed
8192                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8193                    if(StrStr(cps->variants, "boardsize") == NULL) {
8194                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8195                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8196                        DisplayFatalError(buf, 0, 1);
8197                        return;
8198                    }
8199                    /* [HGM] here we really should compare with the maximum supported board size */
8200                }
8201            }
8202       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8203       sprintf(buf, "variant %s\n", b);
8204       SendToProgram(buf, cps);
8205     }
8206     currentlyInitializedVariant = gameInfo.variant;
8207
8208     /* [HGM] send opening position in FRC to first engine */
8209     if(setup) {
8210           SendToProgram("force\n", cps);
8211           SendBoard(cps, 0);
8212           /* engine is now in force mode! Set flag to wake it up after first move. */
8213           setboardSpoiledMachineBlack = 1;
8214     }
8215
8216     if (cps->sendICS) {
8217       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8218       SendToProgram(buf, cps);
8219     }
8220     cps->maybeThinking = FALSE;
8221     cps->offeredDraw = 0;
8222     if (!appData.icsActive) {
8223         SendTimeControl(cps, movesPerSession, timeControl,
8224                         timeIncrement, appData.searchDepth,
8225                         searchTime);
8226     }
8227     if (appData.showThinking 
8228         // [HGM] thinking: four options require thinking output to be sent
8229         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8230                                 ) {
8231         SendToProgram("post\n", cps);
8232     }
8233     SendToProgram("hard\n", cps);
8234     if (!appData.ponderNextMove) {
8235         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8236            it without being sure what state we are in first.  "hard"
8237            is not a toggle, so that one is OK.
8238          */
8239         SendToProgram("easy\n", cps);
8240     }
8241     if (cps->usePing) {
8242       sprintf(buf, "ping %d\n", ++cps->lastPing);
8243       SendToProgram(buf, cps);
8244     }
8245     cps->initDone = TRUE;
8246 }   
8247
8248
8249 void
8250 StartChessProgram(cps)
8251      ChessProgramState *cps;
8252 {
8253     char buf[MSG_SIZ];
8254     int err;
8255
8256     if (appData.noChessProgram) return;
8257     cps->initDone = FALSE;
8258
8259     if (strcmp(cps->host, "localhost") == 0) {
8260         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8261     } else if (*appData.remoteShell == NULLCHAR) {
8262         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8263     } else {
8264         if (*appData.remoteUser == NULLCHAR) {
8265           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8266                     cps->program);
8267         } else {
8268           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8269                     cps->host, appData.remoteUser, cps->program);
8270         }
8271         err = StartChildProcess(buf, "", &cps->pr);
8272     }
8273     
8274     if (err != 0) {
8275         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8276         DisplayFatalError(buf, err, 1);
8277         cps->pr = NoProc;
8278         cps->isr = NULL;
8279         return;
8280     }
8281     
8282     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8283     if (cps->protocolVersion > 1) {
8284       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8285       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8286       cps->comboCnt = 0;  //                and values of combo boxes
8287       SendToProgram(buf, cps);
8288     } else {
8289       SendToProgram("xboard\n", cps);
8290     }
8291 }
8292
8293
8294 void
8295 TwoMachinesEventIfReady P((void))
8296 {
8297   if (first.lastPing != first.lastPong) {
8298     DisplayMessage("", _("Waiting for first chess program"));
8299     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8300     return;
8301   }
8302   if (second.lastPing != second.lastPong) {
8303     DisplayMessage("", _("Waiting for second chess program"));
8304     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8305     return;
8306   }
8307   ThawUI();
8308   TwoMachinesEvent();
8309 }
8310
8311 void
8312 NextMatchGame P((void))
8313 {
8314     int index; /* [HGM] autoinc: step load index during match */
8315     Reset(FALSE, TRUE);
8316     if (*appData.loadGameFile != NULLCHAR) {
8317         index = appData.loadGameIndex;
8318         if(index < 0) { // [HGM] autoinc
8319             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8320             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8321         } 
8322         LoadGameFromFile(appData.loadGameFile,
8323                          index,
8324                          appData.loadGameFile, FALSE);
8325     } else if (*appData.loadPositionFile != NULLCHAR) {
8326         index = appData.loadPositionIndex;
8327         if(index < 0) { // [HGM] autoinc
8328             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8329             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8330         } 
8331         LoadPositionFromFile(appData.loadPositionFile,
8332                              index,
8333                              appData.loadPositionFile);
8334     }
8335     TwoMachinesEventIfReady();
8336 }
8337
8338 void UserAdjudicationEvent( int result )
8339 {
8340     ChessMove gameResult = GameIsDrawn;
8341
8342     if( result > 0 ) {
8343         gameResult = WhiteWins;
8344     }
8345     else if( result < 0 ) {
8346         gameResult = BlackWins;
8347     }
8348
8349     if( gameMode == TwoMachinesPlay ) {
8350         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8351     }
8352 }
8353
8354
8355 // [HGM] save: calculate checksum of game to make games easily identifiable
8356 int StringCheckSum(char *s)
8357 {
8358         int i = 0;
8359         if(s==NULL) return 0;
8360         while(*s) i = i*259 + *s++;
8361         return i;
8362 }
8363
8364 int GameCheckSum()
8365 {
8366         int i, sum=0;
8367         for(i=backwardMostMove; i<forwardMostMove; i++) {
8368                 sum += pvInfoList[i].depth;
8369                 sum += StringCheckSum(parseList[i]);
8370                 sum += StringCheckSum(commentList[i]);
8371                 sum *= 261;
8372         }
8373         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8374         return sum + StringCheckSum(commentList[i]);
8375 } // end of save patch
8376
8377 void
8378 GameEnds(result, resultDetails, whosays)
8379      ChessMove result;
8380      char *resultDetails;
8381      int whosays;
8382 {
8383     GameMode nextGameMode;
8384     int isIcsGame;
8385     char buf[MSG_SIZ];
8386
8387     if(endingGame) return; /* [HGM] crash: forbid recursion */
8388     endingGame = 1;
8389
8390     if (appData.debugMode) {
8391       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8392               result, resultDetails ? resultDetails : "(null)", whosays);
8393     }
8394
8395     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8396         /* If we are playing on ICS, the server decides when the
8397            game is over, but the engine can offer to draw, claim 
8398            a draw, or resign. 
8399          */
8400 #if ZIPPY
8401         if (appData.zippyPlay && first.initDone) {
8402             if (result == GameIsDrawn) {
8403                 /* In case draw still needs to be claimed */
8404                 SendToICS(ics_prefix);
8405                 SendToICS("draw\n");
8406             } else if (StrCaseStr(resultDetails, "resign")) {
8407                 SendToICS(ics_prefix);
8408                 SendToICS("resign\n");
8409             }
8410         }
8411 #endif
8412         endingGame = 0; /* [HGM] crash */
8413         return;
8414     }
8415
8416     /* If we're loading the game from a file, stop */
8417     if (whosays == GE_FILE) {
8418       (void) StopLoadGameTimer();
8419       gameFileFP = NULL;
8420     }
8421
8422     /* Cancel draw offers */
8423     first.offeredDraw = second.offeredDraw = 0;
8424
8425     /* If this is an ICS game, only ICS can really say it's done;
8426        if not, anyone can. */
8427     isIcsGame = (gameMode == IcsPlayingWhite || 
8428                  gameMode == IcsPlayingBlack || 
8429                  gameMode == IcsObserving    || 
8430                  gameMode == IcsExamining);
8431
8432     if (!isIcsGame || whosays == GE_ICS) {
8433         /* OK -- not an ICS game, or ICS said it was done */
8434         StopClocks();
8435         if (!isIcsGame && !appData.noChessProgram) 
8436           SetUserThinkingEnables();
8437     
8438         /* [HGM] if a machine claims the game end we verify this claim */
8439         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8440             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8441                 char claimer;
8442                 ChessMove trueResult = (ChessMove) -1;
8443
8444                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8445                                             first.twoMachinesColor[0] :
8446                                             second.twoMachinesColor[0] ;
8447
8448                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8449                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8450                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8451                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8452                 } else
8453                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8454                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8455                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8456                 } else
8457                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8458                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8459                 }
8460
8461                 // now verify win claims, but not in drop games, as we don't understand those yet
8462                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8463                                                  || gameInfo.variant == VariantGreat) &&
8464                     (result == WhiteWins && claimer == 'w' ||
8465                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8466                       if (appData.debugMode) {
8467                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8468                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8469                       }
8470                       if(result != trueResult) {
8471                               sprintf(buf, "False win claim: '%s'", resultDetails);
8472                               result = claimer == 'w' ? BlackWins : WhiteWins;
8473                               resultDetails = buf;
8474                       }
8475                 } else
8476                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8477                     && (forwardMostMove <= backwardMostMove ||
8478                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8479                         (claimer=='b')==(forwardMostMove&1))
8480                                                                                   ) {
8481                       /* [HGM] verify: draws that were not flagged are false claims */
8482                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8483                       result = claimer == 'w' ? BlackWins : WhiteWins;
8484                       resultDetails = buf;
8485                 }
8486                 /* (Claiming a loss is accepted no questions asked!) */
8487             }
8488             /* [HGM] bare: don't allow bare King to win */
8489             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8490                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8491                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8492                && result != GameIsDrawn)
8493             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8494                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8495                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8496                         if(p >= 0 && p <= (int)WhiteKing) k++;
8497                 }
8498                 if (appData.debugMode) {
8499                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8500                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8501                 }
8502                 if(k <= 1) {
8503                         result = GameIsDrawn;
8504                         sprintf(buf, "%s but bare king", resultDetails);
8505                         resultDetails = buf;
8506                 }
8507             }
8508         }
8509
8510
8511         if(serverMoves != NULL && !loadFlag) { char c = '=';
8512             if(result==WhiteWins) c = '+';
8513             if(result==BlackWins) c = '-';
8514             if(resultDetails != NULL)
8515                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8516         }
8517         if (resultDetails != NULL) {
8518             gameInfo.result = result;
8519             gameInfo.resultDetails = StrSave(resultDetails);
8520
8521             /* display last move only if game was not loaded from file */
8522             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8523                 DisplayMove(currentMove - 1);
8524     
8525             if (forwardMostMove != 0) {
8526                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8527                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8528                                                                 ) {
8529                     if (*appData.saveGameFile != NULLCHAR) {
8530                         SaveGameToFile(appData.saveGameFile, TRUE);
8531                     } else if (appData.autoSaveGames) {
8532                         AutoSaveGame();
8533                     }
8534                     if (*appData.savePositionFile != NULLCHAR) {
8535                         SavePositionToFile(appData.savePositionFile);
8536                     }
8537                 }
8538             }
8539
8540             /* Tell program how game ended in case it is learning */
8541             /* [HGM] Moved this to after saving the PGN, just in case */
8542             /* engine died and we got here through time loss. In that */
8543             /* case we will get a fatal error writing the pipe, which */
8544             /* would otherwise lose us the PGN.                       */
8545             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8546             /* output during GameEnds should never be fatal anymore   */
8547             if (gameMode == MachinePlaysWhite ||
8548                 gameMode == MachinePlaysBlack ||
8549                 gameMode == TwoMachinesPlay ||
8550                 gameMode == IcsPlayingWhite ||
8551                 gameMode == IcsPlayingBlack ||
8552                 gameMode == BeginningOfGame) {
8553                 char buf[MSG_SIZ];
8554                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8555                         resultDetails);
8556                 if (first.pr != NoProc) {
8557                     SendToProgram(buf, &first);
8558                 }
8559                 if (second.pr != NoProc &&
8560                     gameMode == TwoMachinesPlay) {
8561                     SendToProgram(buf, &second);
8562                 }
8563             }
8564         }
8565
8566         if (appData.icsActive) {
8567             if (appData.quietPlay &&
8568                 (gameMode == IcsPlayingWhite ||
8569                  gameMode == IcsPlayingBlack)) {
8570                 SendToICS(ics_prefix);
8571                 SendToICS("set shout 1\n");
8572             }
8573             nextGameMode = IcsIdle;
8574             ics_user_moved = FALSE;
8575             /* clean up premove.  It's ugly when the game has ended and the
8576              * premove highlights are still on the board.
8577              */
8578             if (gotPremove) {
8579               gotPremove = FALSE;
8580               ClearPremoveHighlights();
8581               DrawPosition(FALSE, boards[currentMove]);
8582             }
8583             if (whosays == GE_ICS) {
8584                 switch (result) {
8585                 case WhiteWins:
8586                     if (gameMode == IcsPlayingWhite)
8587                         PlayIcsWinSound();
8588                     else if(gameMode == IcsPlayingBlack)
8589                         PlayIcsLossSound();
8590                     break;
8591                 case BlackWins:
8592                     if (gameMode == IcsPlayingBlack)
8593                         PlayIcsWinSound();
8594                     else if(gameMode == IcsPlayingWhite)
8595                         PlayIcsLossSound();
8596                     break;
8597                 case GameIsDrawn:
8598                     PlayIcsDrawSound();
8599                     break;
8600                 default:
8601                     PlayIcsUnfinishedSound();
8602                 }
8603             }
8604         } else if (gameMode == EditGame ||
8605                    gameMode == PlayFromGameFile || 
8606                    gameMode == AnalyzeMode || 
8607                    gameMode == AnalyzeFile) {
8608             nextGameMode = gameMode;
8609         } else {
8610             nextGameMode = EndOfGame;
8611         }
8612         pausing = FALSE;
8613         ModeHighlight();
8614     } else {
8615         nextGameMode = gameMode;
8616     }
8617
8618     if (appData.noChessProgram) {
8619         gameMode = nextGameMode;
8620         ModeHighlight();
8621         endingGame = 0; /* [HGM] crash */
8622         return;
8623     }
8624
8625     if (first.reuse) {
8626         /* Put first chess program into idle state */
8627         if (first.pr != NoProc &&
8628             (gameMode == MachinePlaysWhite ||
8629              gameMode == MachinePlaysBlack ||
8630              gameMode == TwoMachinesPlay ||
8631              gameMode == IcsPlayingWhite ||
8632              gameMode == IcsPlayingBlack ||
8633              gameMode == BeginningOfGame)) {
8634             SendToProgram("force\n", &first);
8635             if (first.usePing) {
8636               char buf[MSG_SIZ];
8637               sprintf(buf, "ping %d\n", ++first.lastPing);
8638               SendToProgram(buf, &first);
8639             }
8640         }
8641     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8642         /* Kill off first chess program */
8643         if (first.isr != NULL)
8644           RemoveInputSource(first.isr);
8645         first.isr = NULL;
8646     
8647         if (first.pr != NoProc) {
8648             ExitAnalyzeMode();
8649             DoSleep( appData.delayBeforeQuit );
8650             SendToProgram("quit\n", &first);
8651             DoSleep( appData.delayAfterQuit );
8652             DestroyChildProcess(first.pr, first.useSigterm);
8653         }
8654         first.pr = NoProc;
8655     }
8656     if (second.reuse) {
8657         /* Put second chess program into idle state */
8658         if (second.pr != NoProc &&
8659             gameMode == TwoMachinesPlay) {
8660             SendToProgram("force\n", &second);
8661             if (second.usePing) {
8662               char buf[MSG_SIZ];
8663               sprintf(buf, "ping %d\n", ++second.lastPing);
8664               SendToProgram(buf, &second);
8665             }
8666         }
8667     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8668         /* Kill off second chess program */
8669         if (second.isr != NULL)
8670           RemoveInputSource(second.isr);
8671         second.isr = NULL;
8672     
8673         if (second.pr != NoProc) {
8674             DoSleep( appData.delayBeforeQuit );
8675             SendToProgram("quit\n", &second);
8676             DoSleep( appData.delayAfterQuit );
8677             DestroyChildProcess(second.pr, second.useSigterm);
8678         }
8679         second.pr = NoProc;
8680     }
8681
8682     if (matchMode && gameMode == TwoMachinesPlay) {
8683         switch (result) {
8684         case WhiteWins:
8685           if (first.twoMachinesColor[0] == 'w') {
8686             first.matchWins++;
8687           } else {
8688             second.matchWins++;
8689           }
8690           break;
8691         case BlackWins:
8692           if (first.twoMachinesColor[0] == 'b') {
8693             first.matchWins++;
8694           } else {
8695             second.matchWins++;
8696           }
8697           break;
8698         default:
8699           break;
8700         }
8701         if (matchGame < appData.matchGames) {
8702             char *tmp;
8703             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8704                 tmp = first.twoMachinesColor;
8705                 first.twoMachinesColor = second.twoMachinesColor;
8706                 second.twoMachinesColor = tmp;
8707             }
8708             gameMode = nextGameMode;
8709             matchGame++;
8710             if(appData.matchPause>10000 || appData.matchPause<10)
8711                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8712             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8713             endingGame = 0; /* [HGM] crash */
8714             return;
8715         } else {
8716             char buf[MSG_SIZ];
8717             gameMode = nextGameMode;
8718             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8719                     first.tidy, second.tidy,
8720                     first.matchWins, second.matchWins,
8721                     appData.matchGames - (first.matchWins + second.matchWins));
8722             DisplayFatalError(buf, 0, 0);
8723         }
8724     }
8725     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8726         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8727       ExitAnalyzeMode();
8728     gameMode = nextGameMode;
8729     ModeHighlight();
8730     endingGame = 0;  /* [HGM] crash */
8731 }
8732
8733 /* Assumes program was just initialized (initString sent).
8734    Leaves program in force mode. */
8735 void
8736 FeedMovesToProgram(cps, upto) 
8737      ChessProgramState *cps;
8738      int upto;
8739 {
8740     int i;
8741     
8742     if (appData.debugMode)
8743       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8744               startedFromSetupPosition ? "position and " : "",
8745               backwardMostMove, upto, cps->which);
8746     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8747         // [HGM] variantswitch: make engine aware of new variant
8748         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8749                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8750         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8751         SendToProgram(buf, cps);
8752         currentlyInitializedVariant = gameInfo.variant;
8753     }
8754     SendToProgram("force\n", cps);
8755     if (startedFromSetupPosition) {
8756         SendBoard(cps, backwardMostMove);
8757     if (appData.debugMode) {
8758         fprintf(debugFP, "feedMoves\n");
8759     }
8760     }
8761     for (i = backwardMostMove; i < upto; i++) {
8762         SendMoveToProgram(i, cps);
8763     }
8764 }
8765
8766
8767 void
8768 ResurrectChessProgram()
8769 {
8770      /* The chess program may have exited.
8771         If so, restart it and feed it all the moves made so far. */
8772
8773     if (appData.noChessProgram || first.pr != NoProc) return;
8774     
8775     StartChessProgram(&first);
8776     InitChessProgram(&first, FALSE);
8777     FeedMovesToProgram(&first, currentMove);
8778
8779     if (!first.sendTime) {
8780         /* can't tell gnuchess what its clock should read,
8781            so we bow to its notion. */
8782         ResetClocks();
8783         timeRemaining[0][currentMove] = whiteTimeRemaining;
8784         timeRemaining[1][currentMove] = blackTimeRemaining;
8785     }
8786
8787     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8788                 appData.icsEngineAnalyze) && first.analysisSupport) {
8789       SendToProgram("analyze\n", &first);
8790       first.analyzing = TRUE;
8791     }
8792 }
8793
8794 /*
8795  * Button procedures
8796  */
8797 void
8798 Reset(redraw, init)
8799      int redraw, init;
8800 {
8801     int i;
8802
8803     if (appData.debugMode) {
8804         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8805                 redraw, init, gameMode);
8806     }
8807     CleanupTail(); // [HGM] vari: delete any stored variations
8808     pausing = pauseExamInvalid = FALSE;
8809     startedFromSetupPosition = blackPlaysFirst = FALSE;
8810     firstMove = TRUE;
8811     whiteFlag = blackFlag = FALSE;
8812     userOfferedDraw = FALSE;
8813     hintRequested = bookRequested = FALSE;
8814     first.maybeThinking = FALSE;
8815     second.maybeThinking = FALSE;
8816     first.bookSuspend = FALSE; // [HGM] book
8817     second.bookSuspend = FALSE;
8818     thinkOutput[0] = NULLCHAR;
8819     lastHint[0] = NULLCHAR;
8820     ClearGameInfo(&gameInfo);
8821     gameInfo.variant = StringToVariant(appData.variant);
8822     ics_user_moved = ics_clock_paused = FALSE;
8823     ics_getting_history = H_FALSE;
8824     ics_gamenum = -1;
8825     white_holding[0] = black_holding[0] = NULLCHAR;
8826     ClearProgramStats();
8827     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8828     
8829     ResetFrontEnd();
8830     ClearHighlights();
8831     flipView = appData.flipView;
8832     ClearPremoveHighlights();
8833     gotPremove = FALSE;
8834     alarmSounded = FALSE;
8835
8836     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8837     if(appData.serverMovesName != NULL) {
8838         /* [HGM] prepare to make moves file for broadcasting */
8839         clock_t t = clock();
8840         if(serverMoves != NULL) fclose(serverMoves);
8841         serverMoves = fopen(appData.serverMovesName, "r");
8842         if(serverMoves != NULL) {
8843             fclose(serverMoves);
8844             /* delay 15 sec before overwriting, so all clients can see end */
8845             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8846         }
8847         serverMoves = fopen(appData.serverMovesName, "w");
8848     }
8849
8850     ExitAnalyzeMode();
8851     gameMode = BeginningOfGame;
8852     ModeHighlight();
8853     if(appData.icsActive) gameInfo.variant = VariantNormal;
8854     currentMove = forwardMostMove = backwardMostMove = 0;
8855     InitPosition(redraw);
8856     for (i = 0; i < MAX_MOVES; i++) {
8857         if (commentList[i] != NULL) {
8858             free(commentList[i]);
8859             commentList[i] = NULL;
8860         }
8861     }
8862     ResetClocks();
8863     timeRemaining[0][0] = whiteTimeRemaining;
8864     timeRemaining[1][0] = blackTimeRemaining;
8865     if (first.pr == NULL) {
8866         StartChessProgram(&first);
8867     }
8868     if (init) {
8869             InitChessProgram(&first, startedFromSetupPosition);
8870     }
8871     DisplayTitle("");
8872     DisplayMessage("", "");
8873     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8874     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8875 }
8876
8877 void
8878 AutoPlayGameLoop()
8879 {
8880     for (;;) {
8881         if (!AutoPlayOneMove())
8882           return;
8883         if (matchMode || appData.timeDelay == 0)
8884           continue;
8885         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8886           return;
8887         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8888         break;
8889     }
8890 }
8891
8892
8893 int
8894 AutoPlayOneMove()
8895 {
8896     int fromX, fromY, toX, toY;
8897
8898     if (appData.debugMode) {
8899       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8900     }
8901
8902     if (gameMode != PlayFromGameFile)
8903       return FALSE;
8904
8905     if (currentMove >= forwardMostMove) {
8906       gameMode = EditGame;
8907       ModeHighlight();
8908
8909       /* [AS] Clear current move marker at the end of a game */
8910       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8911
8912       return FALSE;
8913     }
8914     
8915     toX = moveList[currentMove][2] - AAA;
8916     toY = moveList[currentMove][3] - ONE;
8917
8918     if (moveList[currentMove][1] == '@') {
8919         if (appData.highlightLastMove) {
8920             SetHighlights(-1, -1, toX, toY);
8921         }
8922     } else {
8923         fromX = moveList[currentMove][0] - AAA;
8924         fromY = moveList[currentMove][1] - ONE;
8925
8926         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8927
8928         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8929
8930         if (appData.highlightLastMove) {
8931             SetHighlights(fromX, fromY, toX, toY);
8932         }
8933     }
8934     DisplayMove(currentMove);
8935     SendMoveToProgram(currentMove++, &first);
8936     DisplayBothClocks();
8937     DrawPosition(FALSE, boards[currentMove]);
8938     // [HGM] PV info: always display, routine tests if empty
8939     DisplayComment(currentMove - 1, commentList[currentMove]);
8940     return TRUE;
8941 }
8942
8943
8944 int
8945 LoadGameOneMove(readAhead)
8946      ChessMove readAhead;
8947 {
8948     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8949     char promoChar = NULLCHAR;
8950     ChessMove moveType;
8951     char move[MSG_SIZ];
8952     char *p, *q;
8953     
8954     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8955         gameMode != AnalyzeMode && gameMode != Training) {
8956         gameFileFP = NULL;
8957         return FALSE;
8958     }
8959     
8960     yyboardindex = forwardMostMove;
8961     if (readAhead != (ChessMove)0) {
8962       moveType = readAhead;
8963     } else {
8964       if (gameFileFP == NULL)
8965           return FALSE;
8966       moveType = (ChessMove) yylex();
8967     }
8968     
8969     done = FALSE;
8970     switch (moveType) {
8971       case Comment:
8972         if (appData.debugMode) 
8973           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8974         p = yy_text;
8975
8976         /* append the comment but don't display it */
8977         AppendComment(currentMove, p, FALSE);
8978         return TRUE;
8979
8980       case WhiteCapturesEnPassant:
8981       case BlackCapturesEnPassant:
8982       case WhitePromotionChancellor:
8983       case BlackPromotionChancellor:
8984       case WhitePromotionArchbishop:
8985       case BlackPromotionArchbishop:
8986       case WhitePromotionCentaur:
8987       case BlackPromotionCentaur:
8988       case WhitePromotionQueen:
8989       case BlackPromotionQueen:
8990       case WhitePromotionRook:
8991       case BlackPromotionRook:
8992       case WhitePromotionBishop:
8993       case BlackPromotionBishop:
8994       case WhitePromotionKnight:
8995       case BlackPromotionKnight:
8996       case WhitePromotionKing:
8997       case BlackPromotionKing:
8998       case NormalMove:
8999       case WhiteKingSideCastle:
9000       case WhiteQueenSideCastle:
9001       case BlackKingSideCastle:
9002       case BlackQueenSideCastle:
9003       case WhiteKingSideCastleWild:
9004       case WhiteQueenSideCastleWild:
9005       case BlackKingSideCastleWild:
9006       case BlackQueenSideCastleWild:
9007       /* PUSH Fabien */
9008       case WhiteHSideCastleFR:
9009       case WhiteASideCastleFR:
9010       case BlackHSideCastleFR:
9011       case BlackASideCastleFR:
9012       /* POP Fabien */
9013         if (appData.debugMode)
9014           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9015         fromX = currentMoveString[0] - AAA;
9016         fromY = currentMoveString[1] - ONE;
9017         toX = currentMoveString[2] - AAA;
9018         toY = currentMoveString[3] - ONE;
9019         promoChar = currentMoveString[4];
9020         break;
9021
9022       case WhiteDrop:
9023       case BlackDrop:
9024         if (appData.debugMode)
9025           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9026         fromX = moveType == WhiteDrop ?
9027           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9028         (int) CharToPiece(ToLower(currentMoveString[0]));
9029         fromY = DROP_RANK;
9030         toX = currentMoveString[2] - AAA;
9031         toY = currentMoveString[3] - ONE;
9032         break;
9033
9034       case WhiteWins:
9035       case BlackWins:
9036       case GameIsDrawn:
9037       case GameUnfinished:
9038         if (appData.debugMode)
9039           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9040         p = strchr(yy_text, '{');
9041         if (p == NULL) p = strchr(yy_text, '(');
9042         if (p == NULL) {
9043             p = yy_text;
9044             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9045         } else {
9046             q = strchr(p, *p == '{' ? '}' : ')');
9047             if (q != NULL) *q = NULLCHAR;
9048             p++;
9049         }
9050         GameEnds(moveType, p, GE_FILE);
9051         done = TRUE;
9052         if (cmailMsgLoaded) {
9053             ClearHighlights();
9054             flipView = WhiteOnMove(currentMove);
9055             if (moveType == GameUnfinished) flipView = !flipView;
9056             if (appData.debugMode)
9057               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9058         }
9059         break;
9060
9061       case (ChessMove) 0:       /* end of file */
9062         if (appData.debugMode)
9063           fprintf(debugFP, "Parser hit end of file\n");
9064         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9065           case MT_NONE:
9066           case MT_CHECK:
9067             break;
9068           case MT_CHECKMATE:
9069           case MT_STAINMATE:
9070             if (WhiteOnMove(currentMove)) {
9071                 GameEnds(BlackWins, "Black mates", GE_FILE);
9072             } else {
9073                 GameEnds(WhiteWins, "White mates", GE_FILE);
9074             }
9075             break;
9076           case MT_STALEMATE:
9077             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9078             break;
9079         }
9080         done = TRUE;
9081         break;
9082
9083       case MoveNumberOne:
9084         if (lastLoadGameStart == GNUChessGame) {
9085             /* GNUChessGames have numbers, but they aren't move numbers */
9086             if (appData.debugMode)
9087               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9088                       yy_text, (int) moveType);
9089             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9090         }
9091         /* else fall thru */
9092
9093       case XBoardGame:
9094       case GNUChessGame:
9095       case PGNTag:
9096         /* Reached start of next game in file */
9097         if (appData.debugMode)
9098           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9099         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9100           case MT_NONE:
9101           case MT_CHECK:
9102             break;
9103           case MT_CHECKMATE:
9104           case MT_STAINMATE:
9105             if (WhiteOnMove(currentMove)) {
9106                 GameEnds(BlackWins, "Black mates", GE_FILE);
9107             } else {
9108                 GameEnds(WhiteWins, "White mates", GE_FILE);
9109             }
9110             break;
9111           case MT_STALEMATE:
9112             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9113             break;
9114         }
9115         done = TRUE;
9116         break;
9117
9118       case PositionDiagram:     /* should not happen; ignore */
9119       case ElapsedTime:         /* ignore */
9120       case NAG:                 /* ignore */
9121         if (appData.debugMode)
9122           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9123                   yy_text, (int) moveType);
9124         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9125
9126       case IllegalMove:
9127         if (appData.testLegality) {
9128             if (appData.debugMode)
9129               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9130             sprintf(move, _("Illegal move: %d.%s%s"),
9131                     (forwardMostMove / 2) + 1,
9132                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9133             DisplayError(move, 0);
9134             done = TRUE;
9135         } else {
9136             if (appData.debugMode)
9137               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9138                       yy_text, currentMoveString);
9139             fromX = currentMoveString[0] - AAA;
9140             fromY = currentMoveString[1] - ONE;
9141             toX = currentMoveString[2] - AAA;
9142             toY = currentMoveString[3] - ONE;
9143             promoChar = currentMoveString[4];
9144         }
9145         break;
9146
9147       case AmbiguousMove:
9148         if (appData.debugMode)
9149           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9150         sprintf(move, _("Ambiguous move: %d.%s%s"),
9151                 (forwardMostMove / 2) + 1,
9152                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9153         DisplayError(move, 0);
9154         done = TRUE;
9155         break;
9156
9157       default:
9158       case ImpossibleMove:
9159         if (appData.debugMode)
9160           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9161         sprintf(move, _("Illegal move: %d.%s%s"),
9162                 (forwardMostMove / 2) + 1,
9163                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9164         DisplayError(move, 0);
9165         done = TRUE;
9166         break;
9167     }
9168
9169     if (done) {
9170         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9171             DrawPosition(FALSE, boards[currentMove]);
9172             DisplayBothClocks();
9173             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9174               DisplayComment(currentMove - 1, commentList[currentMove]);
9175         }
9176         (void) StopLoadGameTimer();
9177         gameFileFP = NULL;
9178         cmailOldMove = forwardMostMove;
9179         return FALSE;
9180     } else {
9181         /* currentMoveString is set as a side-effect of yylex */
9182         strcat(currentMoveString, "\n");
9183         strcpy(moveList[forwardMostMove], currentMoveString);
9184         
9185         thinkOutput[0] = NULLCHAR;
9186         MakeMove(fromX, fromY, toX, toY, promoChar);
9187         currentMove = forwardMostMove;
9188         return TRUE;
9189     }
9190 }
9191
9192 /* Load the nth game from the given file */
9193 int
9194 LoadGameFromFile(filename, n, title, useList)
9195      char *filename;
9196      int n;
9197      char *title;
9198      /*Boolean*/ int useList;
9199 {
9200     FILE *f;
9201     char buf[MSG_SIZ];
9202
9203     if (strcmp(filename, "-") == 0) {
9204         f = stdin;
9205         title = "stdin";
9206     } else {
9207         f = fopen(filename, "rb");
9208         if (f == NULL) {
9209           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9210             DisplayError(buf, errno);
9211             return FALSE;
9212         }
9213     }
9214     if (fseek(f, 0, 0) == -1) {
9215         /* f is not seekable; probably a pipe */
9216         useList = FALSE;
9217     }
9218     if (useList && n == 0) {
9219         int error = GameListBuild(f);
9220         if (error) {
9221             DisplayError(_("Cannot build game list"), error);
9222         } else if (!ListEmpty(&gameList) &&
9223                    ((ListGame *) gameList.tailPred)->number > 1) {
9224             GameListPopUp(f, title);
9225             return TRUE;
9226         }
9227         GameListDestroy();
9228         n = 1;
9229     }
9230     if (n == 0) n = 1;
9231     return LoadGame(f, n, title, FALSE);
9232 }
9233
9234
9235 void
9236 MakeRegisteredMove()
9237 {
9238     int fromX, fromY, toX, toY;
9239     char promoChar;
9240     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9241         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9242           case CMAIL_MOVE:
9243           case CMAIL_DRAW:
9244             if (appData.debugMode)
9245               fprintf(debugFP, "Restoring %s for game %d\n",
9246                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9247     
9248             thinkOutput[0] = NULLCHAR;
9249             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9250             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9251             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9252             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9253             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9254             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9255             MakeMove(fromX, fromY, toX, toY, promoChar);
9256             ShowMove(fromX, fromY, toX, toY);
9257               
9258             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9259               case MT_NONE:
9260               case MT_CHECK:
9261                 break;
9262                 
9263               case MT_CHECKMATE:
9264               case MT_STAINMATE:
9265                 if (WhiteOnMove(currentMove)) {
9266                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9267                 } else {
9268                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9269                 }
9270                 break;
9271                 
9272               case MT_STALEMATE:
9273                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9274                 break;
9275             }
9276
9277             break;
9278             
9279           case CMAIL_RESIGN:
9280             if (WhiteOnMove(currentMove)) {
9281                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9282             } else {
9283                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9284             }
9285             break;
9286             
9287           case CMAIL_ACCEPT:
9288             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9289             break;
9290               
9291           default:
9292             break;
9293         }
9294     }
9295
9296     return;
9297 }
9298
9299 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9300 int
9301 CmailLoadGame(f, gameNumber, title, useList)
9302      FILE *f;
9303      int gameNumber;
9304      char *title;
9305      int useList;
9306 {
9307     int retVal;
9308
9309     if (gameNumber > nCmailGames) {
9310         DisplayError(_("No more games in this message"), 0);
9311         return FALSE;
9312     }
9313     if (f == lastLoadGameFP) {
9314         int offset = gameNumber - lastLoadGameNumber;
9315         if (offset == 0) {
9316             cmailMsg[0] = NULLCHAR;
9317             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9318                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9319                 nCmailMovesRegistered--;
9320             }
9321             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9322             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9323                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9324             }
9325         } else {
9326             if (! RegisterMove()) return FALSE;
9327         }
9328     }
9329
9330     retVal = LoadGame(f, gameNumber, title, useList);
9331
9332     /* Make move registered during previous look at this game, if any */
9333     MakeRegisteredMove();
9334
9335     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9336         commentList[currentMove]
9337           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9338         DisplayComment(currentMove - 1, commentList[currentMove]);
9339     }
9340
9341     return retVal;
9342 }
9343
9344 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9345 int
9346 ReloadGame(offset)
9347      int offset;
9348 {
9349     int gameNumber = lastLoadGameNumber + offset;
9350     if (lastLoadGameFP == NULL) {
9351         DisplayError(_("No game has been loaded yet"), 0);
9352         return FALSE;
9353     }
9354     if (gameNumber <= 0) {
9355         DisplayError(_("Can't back up any further"), 0);
9356         return FALSE;
9357     }
9358     if (cmailMsgLoaded) {
9359         return CmailLoadGame(lastLoadGameFP, gameNumber,
9360                              lastLoadGameTitle, lastLoadGameUseList);
9361     } else {
9362         return LoadGame(lastLoadGameFP, gameNumber,
9363                         lastLoadGameTitle, lastLoadGameUseList);
9364     }
9365 }
9366
9367
9368
9369 /* Load the nth game from open file f */
9370 int
9371 LoadGame(f, gameNumber, title, useList)
9372      FILE *f;
9373      int gameNumber;
9374      char *title;
9375      int useList;
9376 {
9377     ChessMove cm;
9378     char buf[MSG_SIZ];
9379     int gn = gameNumber;
9380     ListGame *lg = NULL;
9381     int numPGNTags = 0;
9382     int err;
9383     GameMode oldGameMode;
9384     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9385
9386     if (appData.debugMode) 
9387         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9388
9389     if (gameMode == Training )
9390         SetTrainingModeOff();
9391
9392     oldGameMode = gameMode;
9393     if (gameMode != BeginningOfGame) {
9394       Reset(FALSE, TRUE);
9395     }
9396
9397     gameFileFP = f;
9398     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9399         fclose(lastLoadGameFP);
9400     }
9401
9402     if (useList) {
9403         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9404         
9405         if (lg) {
9406             fseek(f, lg->offset, 0);
9407             GameListHighlight(gameNumber);
9408             gn = 1;
9409         }
9410         else {
9411             DisplayError(_("Game number out of range"), 0);
9412             return FALSE;
9413         }
9414     } else {
9415         GameListDestroy();
9416         if (fseek(f, 0, 0) == -1) {
9417             if (f == lastLoadGameFP ?
9418                 gameNumber == lastLoadGameNumber + 1 :
9419                 gameNumber == 1) {
9420                 gn = 1;
9421             } else {
9422                 DisplayError(_("Can't seek on game file"), 0);
9423                 return FALSE;
9424             }
9425         }
9426     }
9427     lastLoadGameFP = f;
9428     lastLoadGameNumber = gameNumber;
9429     strcpy(lastLoadGameTitle, title);
9430     lastLoadGameUseList = useList;
9431
9432     yynewfile(f);
9433
9434     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9435       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9436                 lg->gameInfo.black);
9437             DisplayTitle(buf);
9438     } else if (*title != NULLCHAR) {
9439         if (gameNumber > 1) {
9440             sprintf(buf, "%s %d", title, gameNumber);
9441             DisplayTitle(buf);
9442         } else {
9443             DisplayTitle(title);
9444         }
9445     }
9446
9447     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9448         gameMode = PlayFromGameFile;
9449         ModeHighlight();
9450     }
9451
9452     currentMove = forwardMostMove = backwardMostMove = 0;
9453     CopyBoard(boards[0], initialPosition);
9454     StopClocks();
9455
9456     /*
9457      * Skip the first gn-1 games in the file.
9458      * Also skip over anything that precedes an identifiable 
9459      * start of game marker, to avoid being confused by 
9460      * garbage at the start of the file.  Currently 
9461      * recognized start of game markers are the move number "1",
9462      * the pattern "gnuchess .* game", the pattern
9463      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9464      * A game that starts with one of the latter two patterns
9465      * will also have a move number 1, possibly
9466      * following a position diagram.
9467      * 5-4-02: Let's try being more lenient and allowing a game to
9468      * start with an unnumbered move.  Does that break anything?
9469      */
9470     cm = lastLoadGameStart = (ChessMove) 0;
9471     while (gn > 0) {
9472         yyboardindex = forwardMostMove;
9473         cm = (ChessMove) yylex();
9474         switch (cm) {
9475           case (ChessMove) 0:
9476             if (cmailMsgLoaded) {
9477                 nCmailGames = CMAIL_MAX_GAMES - gn;
9478             } else {
9479                 Reset(TRUE, TRUE);
9480                 DisplayError(_("Game not found in file"), 0);
9481             }
9482             return FALSE;
9483
9484           case GNUChessGame:
9485           case XBoardGame:
9486             gn--;
9487             lastLoadGameStart = cm;
9488             break;
9489             
9490           case MoveNumberOne:
9491             switch (lastLoadGameStart) {
9492               case GNUChessGame:
9493               case XBoardGame:
9494               case PGNTag:
9495                 break;
9496               case MoveNumberOne:
9497               case (ChessMove) 0:
9498                 gn--;           /* count this game */
9499                 lastLoadGameStart = cm;
9500                 break;
9501               default:
9502                 /* impossible */
9503                 break;
9504             }
9505             break;
9506
9507           case PGNTag:
9508             switch (lastLoadGameStart) {
9509               case GNUChessGame:
9510               case PGNTag:
9511               case MoveNumberOne:
9512               case (ChessMove) 0:
9513                 gn--;           /* count this game */
9514                 lastLoadGameStart = cm;
9515                 break;
9516               case XBoardGame:
9517                 lastLoadGameStart = cm; /* game counted already */
9518                 break;
9519               default:
9520                 /* impossible */
9521                 break;
9522             }
9523             if (gn > 0) {
9524                 do {
9525                     yyboardindex = forwardMostMove;
9526                     cm = (ChessMove) yylex();
9527                 } while (cm == PGNTag || cm == Comment);
9528             }
9529             break;
9530
9531           case WhiteWins:
9532           case BlackWins:
9533           case GameIsDrawn:
9534             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9535                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9536                     != CMAIL_OLD_RESULT) {
9537                     nCmailResults ++ ;
9538                     cmailResult[  CMAIL_MAX_GAMES
9539                                 - gn - 1] = CMAIL_OLD_RESULT;
9540                 }
9541             }
9542             break;
9543
9544           case NormalMove:
9545             /* Only a NormalMove can be at the start of a game
9546              * without a position diagram. */
9547             if (lastLoadGameStart == (ChessMove) 0) {
9548               gn--;
9549               lastLoadGameStart = MoveNumberOne;
9550             }
9551             break;
9552
9553           default:
9554             break;
9555         }
9556     }
9557     
9558     if (appData.debugMode)
9559       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9560
9561     if (cm == XBoardGame) {
9562         /* Skip any header junk before position diagram and/or move 1 */
9563         for (;;) {
9564             yyboardindex = forwardMostMove;
9565             cm = (ChessMove) yylex();
9566
9567             if (cm == (ChessMove) 0 ||
9568                 cm == GNUChessGame || cm == XBoardGame) {
9569                 /* Empty game; pretend end-of-file and handle later */
9570                 cm = (ChessMove) 0;
9571                 break;
9572             }
9573
9574             if (cm == MoveNumberOne || cm == PositionDiagram ||
9575                 cm == PGNTag || cm == Comment)
9576               break;
9577         }
9578     } else if (cm == GNUChessGame) {
9579         if (gameInfo.event != NULL) {
9580             free(gameInfo.event);
9581         }
9582         gameInfo.event = StrSave(yy_text);
9583     }   
9584
9585     startedFromSetupPosition = FALSE;
9586     while (cm == PGNTag) {
9587         if (appData.debugMode) 
9588           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9589         err = ParsePGNTag(yy_text, &gameInfo);
9590         if (!err) numPGNTags++;
9591
9592         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9593         if(gameInfo.variant != oldVariant) {
9594             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9595             InitPosition(TRUE);
9596             oldVariant = gameInfo.variant;
9597             if (appData.debugMode) 
9598               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9599         }
9600
9601
9602         if (gameInfo.fen != NULL) {
9603           Board initial_position;
9604           startedFromSetupPosition = TRUE;
9605           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9606             Reset(TRUE, TRUE);
9607             DisplayError(_("Bad FEN position in file"), 0);
9608             return FALSE;
9609           }
9610           CopyBoard(boards[0], initial_position);
9611           if (blackPlaysFirst) {
9612             currentMove = forwardMostMove = backwardMostMove = 1;
9613             CopyBoard(boards[1], initial_position);
9614             strcpy(moveList[0], "");
9615             strcpy(parseList[0], "");
9616             timeRemaining[0][1] = whiteTimeRemaining;
9617             timeRemaining[1][1] = blackTimeRemaining;
9618             if (commentList[0] != NULL) {
9619               commentList[1] = commentList[0];
9620               commentList[0] = NULL;
9621             }
9622           } else {
9623             currentMove = forwardMostMove = backwardMostMove = 0;
9624           }
9625           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9626           {   int i;
9627               initialRulePlies = FENrulePlies;
9628               for( i=0; i< nrCastlingRights; i++ )
9629                   initialRights[i] = initial_position[CASTLING][i];
9630           }
9631           yyboardindex = forwardMostMove;
9632           free(gameInfo.fen);
9633           gameInfo.fen = NULL;
9634         }
9635
9636         yyboardindex = forwardMostMove;
9637         cm = (ChessMove) yylex();
9638
9639         /* Handle comments interspersed among the tags */
9640         while (cm == Comment) {
9641             char *p;
9642             if (appData.debugMode) 
9643               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9644             p = yy_text;
9645             AppendComment(currentMove, p, FALSE);
9646             yyboardindex = forwardMostMove;
9647             cm = (ChessMove) yylex();
9648         }
9649     }
9650
9651     /* don't rely on existence of Event tag since if game was
9652      * pasted from clipboard the Event tag may not exist
9653      */
9654     if (numPGNTags > 0){
9655         char *tags;
9656         if (gameInfo.variant == VariantNormal) {
9657           gameInfo.variant = StringToVariant(gameInfo.event);
9658         }
9659         if (!matchMode) {
9660           if( appData.autoDisplayTags ) {
9661             tags = PGNTags(&gameInfo);
9662             TagsPopUp(tags, CmailMsg());
9663             free(tags);
9664           }
9665         }
9666     } else {
9667         /* Make something up, but don't display it now */
9668         SetGameInfo();
9669         TagsPopDown();
9670     }
9671
9672     if (cm == PositionDiagram) {
9673         int i, j;
9674         char *p;
9675         Board initial_position;
9676
9677         if (appData.debugMode)
9678           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9679
9680         if (!startedFromSetupPosition) {
9681             p = yy_text;
9682             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9683               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9684                 switch (*p) {
9685                   case '[':
9686                   case '-':
9687                   case ' ':
9688                   case '\t':
9689                   case '\n':
9690                   case '\r':
9691                     break;
9692                   default:
9693                     initial_position[i][j++] = CharToPiece(*p);
9694                     break;
9695                 }
9696             while (*p == ' ' || *p == '\t' ||
9697                    *p == '\n' || *p == '\r') p++;
9698         
9699             if (strncmp(p, "black", strlen("black"))==0)
9700               blackPlaysFirst = TRUE;
9701             else
9702               blackPlaysFirst = FALSE;
9703             startedFromSetupPosition = TRUE;
9704         
9705             CopyBoard(boards[0], initial_position);
9706             if (blackPlaysFirst) {
9707                 currentMove = forwardMostMove = backwardMostMove = 1;
9708                 CopyBoard(boards[1], initial_position);
9709                 strcpy(moveList[0], "");
9710                 strcpy(parseList[0], "");
9711                 timeRemaining[0][1] = whiteTimeRemaining;
9712                 timeRemaining[1][1] = blackTimeRemaining;
9713                 if (commentList[0] != NULL) {
9714                     commentList[1] = commentList[0];
9715                     commentList[0] = NULL;
9716                 }
9717             } else {
9718                 currentMove = forwardMostMove = backwardMostMove = 0;
9719             }
9720         }
9721         yyboardindex = forwardMostMove;
9722         cm = (ChessMove) yylex();
9723     }
9724
9725     if (first.pr == NoProc) {
9726         StartChessProgram(&first);
9727     }
9728     InitChessProgram(&first, FALSE);
9729     SendToProgram("force\n", &first);
9730     if (startedFromSetupPosition) {
9731         SendBoard(&first, forwardMostMove);
9732     if (appData.debugMode) {
9733         fprintf(debugFP, "Load Game\n");
9734     }
9735         DisplayBothClocks();
9736     }      
9737
9738     /* [HGM] server: flag to write setup moves in broadcast file as one */
9739     loadFlag = appData.suppressLoadMoves;
9740
9741     while (cm == Comment) {
9742         char *p;
9743         if (appData.debugMode) 
9744           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9745         p = yy_text;
9746         AppendComment(currentMove, p, FALSE);
9747         yyboardindex = forwardMostMove;
9748         cm = (ChessMove) yylex();
9749     }
9750
9751     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9752         cm == WhiteWins || cm == BlackWins ||
9753         cm == GameIsDrawn || cm == GameUnfinished) {
9754         DisplayMessage("", _("No moves in game"));
9755         if (cmailMsgLoaded) {
9756             if (appData.debugMode)
9757               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9758             ClearHighlights();
9759             flipView = FALSE;
9760         }
9761         DrawPosition(FALSE, boards[currentMove]);
9762         DisplayBothClocks();
9763         gameMode = EditGame;
9764         ModeHighlight();
9765         gameFileFP = NULL;
9766         cmailOldMove = 0;
9767         return TRUE;
9768     }
9769
9770     // [HGM] PV info: routine tests if comment empty
9771     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9772         DisplayComment(currentMove - 1, commentList[currentMove]);
9773     }
9774     if (!matchMode && appData.timeDelay != 0) 
9775       DrawPosition(FALSE, boards[currentMove]);
9776
9777     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9778       programStats.ok_to_send = 1;
9779     }
9780
9781     /* if the first token after the PGN tags is a move
9782      * and not move number 1, retrieve it from the parser 
9783      */
9784     if (cm != MoveNumberOne)
9785         LoadGameOneMove(cm);
9786
9787     /* load the remaining moves from the file */
9788     while (LoadGameOneMove((ChessMove)0)) {
9789       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9790       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9791     }
9792
9793     /* rewind to the start of the game */
9794     currentMove = backwardMostMove;
9795
9796     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9797
9798     if (oldGameMode == AnalyzeFile ||
9799         oldGameMode == AnalyzeMode) {
9800       AnalyzeFileEvent();
9801     }
9802
9803     if (matchMode || appData.timeDelay == 0) {
9804       ToEndEvent();
9805       gameMode = EditGame;
9806       ModeHighlight();
9807     } else if (appData.timeDelay > 0) {
9808       AutoPlayGameLoop();
9809     }
9810
9811     if (appData.debugMode) 
9812         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9813
9814     loadFlag = 0; /* [HGM] true game starts */
9815     return TRUE;
9816 }
9817
9818 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9819 int
9820 ReloadPosition(offset)
9821      int offset;
9822 {
9823     int positionNumber = lastLoadPositionNumber + offset;
9824     if (lastLoadPositionFP == NULL) {
9825         DisplayError(_("No position has been loaded yet"), 0);
9826         return FALSE;
9827     }
9828     if (positionNumber <= 0) {
9829         DisplayError(_("Can't back up any further"), 0);
9830         return FALSE;
9831     }
9832     return LoadPosition(lastLoadPositionFP, positionNumber,
9833                         lastLoadPositionTitle);
9834 }
9835
9836 /* Load the nth position from the given file */
9837 int
9838 LoadPositionFromFile(filename, n, title)
9839      char *filename;
9840      int n;
9841      char *title;
9842 {
9843     FILE *f;
9844     char buf[MSG_SIZ];
9845
9846     if (strcmp(filename, "-") == 0) {
9847         return LoadPosition(stdin, n, "stdin");
9848     } else {
9849         f = fopen(filename, "rb");
9850         if (f == NULL) {
9851             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9852             DisplayError(buf, errno);
9853             return FALSE;
9854         } else {
9855             return LoadPosition(f, n, title);
9856         }
9857     }
9858 }
9859
9860 /* Load the nth position from the given open file, and close it */
9861 int
9862 LoadPosition(f, positionNumber, title)
9863      FILE *f;
9864      int positionNumber;
9865      char *title;
9866 {
9867     char *p, line[MSG_SIZ];
9868     Board initial_position;
9869     int i, j, fenMode, pn;
9870     
9871     if (gameMode == Training )
9872         SetTrainingModeOff();
9873
9874     if (gameMode != BeginningOfGame) {
9875         Reset(FALSE, TRUE);
9876     }
9877     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9878         fclose(lastLoadPositionFP);
9879     }
9880     if (positionNumber == 0) positionNumber = 1;
9881     lastLoadPositionFP = f;
9882     lastLoadPositionNumber = positionNumber;
9883     strcpy(lastLoadPositionTitle, title);
9884     if (first.pr == NoProc) {
9885       StartChessProgram(&first);
9886       InitChessProgram(&first, FALSE);
9887     }    
9888     pn = positionNumber;
9889     if (positionNumber < 0) {
9890         /* Negative position number means to seek to that byte offset */
9891         if (fseek(f, -positionNumber, 0) == -1) {
9892             DisplayError(_("Can't seek on position file"), 0);
9893             return FALSE;
9894         };
9895         pn = 1;
9896     } else {
9897         if (fseek(f, 0, 0) == -1) {
9898             if (f == lastLoadPositionFP ?
9899                 positionNumber == lastLoadPositionNumber + 1 :
9900                 positionNumber == 1) {
9901                 pn = 1;
9902             } else {
9903                 DisplayError(_("Can't seek on position file"), 0);
9904                 return FALSE;
9905             }
9906         }
9907     }
9908     /* See if this file is FEN or old-style xboard */
9909     if (fgets(line, MSG_SIZ, f) == NULL) {
9910         DisplayError(_("Position not found in file"), 0);
9911         return FALSE;
9912     }
9913     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9914     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9915
9916     if (pn >= 2) {
9917         if (fenMode || line[0] == '#') pn--;
9918         while (pn > 0) {
9919             /* skip positions before number pn */
9920             if (fgets(line, MSG_SIZ, f) == NULL) {
9921                 Reset(TRUE, TRUE);
9922                 DisplayError(_("Position not found in file"), 0);
9923                 return FALSE;
9924             }
9925             if (fenMode || line[0] == '#') pn--;
9926         }
9927     }
9928
9929     if (fenMode) {
9930         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9931             DisplayError(_("Bad FEN position in file"), 0);
9932             return FALSE;
9933         }
9934     } else {
9935         (void) fgets(line, MSG_SIZ, f);
9936         (void) fgets(line, MSG_SIZ, f);
9937     
9938         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9939             (void) fgets(line, MSG_SIZ, f);
9940             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9941                 if (*p == ' ')
9942                   continue;
9943                 initial_position[i][j++] = CharToPiece(*p);
9944             }
9945         }
9946     
9947         blackPlaysFirst = FALSE;
9948         if (!feof(f)) {
9949             (void) fgets(line, MSG_SIZ, f);
9950             if (strncmp(line, "black", strlen("black"))==0)
9951               blackPlaysFirst = TRUE;
9952         }
9953     }
9954     startedFromSetupPosition = TRUE;
9955     
9956     SendToProgram("force\n", &first);
9957     CopyBoard(boards[0], initial_position);
9958     if (blackPlaysFirst) {
9959         currentMove = forwardMostMove = backwardMostMove = 1;
9960         strcpy(moveList[0], "");
9961         strcpy(parseList[0], "");
9962         CopyBoard(boards[1], initial_position);
9963         DisplayMessage("", _("Black to play"));
9964     } else {
9965         currentMove = forwardMostMove = backwardMostMove = 0;
9966         DisplayMessage("", _("White to play"));
9967     }
9968     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9969     SendBoard(&first, forwardMostMove);
9970     if (appData.debugMode) {
9971 int i, j;
9972   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9973   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9974         fprintf(debugFP, "Load Position\n");
9975     }
9976
9977     if (positionNumber > 1) {
9978         sprintf(line, "%s %d", title, positionNumber);
9979         DisplayTitle(line);
9980     } else {
9981         DisplayTitle(title);
9982     }
9983     gameMode = EditGame;
9984     ModeHighlight();
9985     ResetClocks();
9986     timeRemaining[0][1] = whiteTimeRemaining;
9987     timeRemaining[1][1] = blackTimeRemaining;
9988     DrawPosition(FALSE, boards[currentMove]);
9989    
9990     return TRUE;
9991 }
9992
9993
9994 void
9995 CopyPlayerNameIntoFileName(dest, src)
9996      char **dest, *src;
9997 {
9998     while (*src != NULLCHAR && *src != ',') {
9999         if (*src == ' ') {
10000             *(*dest)++ = '_';
10001             src++;
10002         } else {
10003             *(*dest)++ = *src++;
10004         }
10005     }
10006 }
10007
10008 char *DefaultFileName(ext)
10009      char *ext;
10010 {
10011     static char def[MSG_SIZ];
10012     char *p;
10013
10014     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10015         p = def;
10016         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10017         *p++ = '-';
10018         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10019         *p++ = '.';
10020         strcpy(p, ext);
10021     } else {
10022         def[0] = NULLCHAR;
10023     }
10024     return def;
10025 }
10026
10027 /* Save the current game to the given file */
10028 int
10029 SaveGameToFile(filename, append)
10030      char *filename;
10031      int append;
10032 {
10033     FILE *f;
10034     char buf[MSG_SIZ];
10035
10036     if (strcmp(filename, "-") == 0) {
10037         return SaveGame(stdout, 0, NULL);
10038     } else {
10039         f = fopen(filename, append ? "a" : "w");
10040         if (f == NULL) {
10041             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10042             DisplayError(buf, errno);
10043             return FALSE;
10044         } else {
10045             return SaveGame(f, 0, NULL);
10046         }
10047     }
10048 }
10049
10050 char *
10051 SavePart(str)
10052      char *str;
10053 {
10054     static char buf[MSG_SIZ];
10055     char *p;
10056     
10057     p = strchr(str, ' ');
10058     if (p == NULL) return str;
10059     strncpy(buf, str, p - str);
10060     buf[p - str] = NULLCHAR;
10061     return buf;
10062 }
10063
10064 #define PGN_MAX_LINE 75
10065
10066 #define PGN_SIDE_WHITE  0
10067 #define PGN_SIDE_BLACK  1
10068
10069 /* [AS] */
10070 static int FindFirstMoveOutOfBook( int side )
10071 {
10072     int result = -1;
10073
10074     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10075         int index = backwardMostMove;
10076         int has_book_hit = 0;
10077
10078         if( (index % 2) != side ) {
10079             index++;
10080         }
10081
10082         while( index < forwardMostMove ) {
10083             /* Check to see if engine is in book */
10084             int depth = pvInfoList[index].depth;
10085             int score = pvInfoList[index].score;
10086             int in_book = 0;
10087
10088             if( depth <= 2 ) {
10089                 in_book = 1;
10090             }
10091             else if( score == 0 && depth == 63 ) {
10092                 in_book = 1; /* Zappa */
10093             }
10094             else if( score == 2 && depth == 99 ) {
10095                 in_book = 1; /* Abrok */
10096             }
10097
10098             has_book_hit += in_book;
10099
10100             if( ! in_book ) {
10101                 result = index;
10102
10103                 break;
10104             }
10105
10106             index += 2;
10107         }
10108     }
10109
10110     return result;
10111 }
10112
10113 /* [AS] */
10114 void GetOutOfBookInfo( char * buf )
10115 {
10116     int oob[2];
10117     int i;
10118     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10119
10120     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10121     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10122
10123     *buf = '\0';
10124
10125     if( oob[0] >= 0 || oob[1] >= 0 ) {
10126         for( i=0; i<2; i++ ) {
10127             int idx = oob[i];
10128
10129             if( idx >= 0 ) {
10130                 if( i > 0 && oob[0] >= 0 ) {
10131                     strcat( buf, "   " );
10132                 }
10133
10134                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10135                 sprintf( buf+strlen(buf), "%s%.2f", 
10136                     pvInfoList[idx].score >= 0 ? "+" : "",
10137                     pvInfoList[idx].score / 100.0 );
10138             }
10139         }
10140     }
10141 }
10142
10143 /* Save game in PGN style and close the file */
10144 int
10145 SaveGamePGN(f)
10146      FILE *f;
10147 {
10148     int i, offset, linelen, newblock;
10149     time_t tm;
10150 //    char *movetext;
10151     char numtext[32];
10152     int movelen, numlen, blank;
10153     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10154
10155     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10156     
10157     tm = time((time_t *) NULL);
10158     
10159     PrintPGNTags(f, &gameInfo);
10160     
10161     if (backwardMostMove > 0 || startedFromSetupPosition) {
10162         char *fen = PositionToFEN(backwardMostMove, NULL);
10163         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10164         fprintf(f, "\n{--------------\n");
10165         PrintPosition(f, backwardMostMove);
10166         fprintf(f, "--------------}\n");
10167         free(fen);
10168     }
10169     else {
10170         /* [AS] Out of book annotation */
10171         if( appData.saveOutOfBookInfo ) {
10172             char buf[64];
10173
10174             GetOutOfBookInfo( buf );
10175
10176             if( buf[0] != '\0' ) {
10177                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10178             }
10179         }
10180
10181         fprintf(f, "\n");
10182     }
10183
10184     i = backwardMostMove;
10185     linelen = 0;
10186     newblock = TRUE;
10187
10188     while (i < forwardMostMove) {
10189         /* Print comments preceding this move */
10190         if (commentList[i] != NULL) {
10191             if (linelen > 0) fprintf(f, "\n");
10192             fprintf(f, "%s", commentList[i]);
10193             linelen = 0;
10194             newblock = TRUE;
10195         }
10196
10197         /* Format move number */
10198         if ((i % 2) == 0) {
10199             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10200         } else {
10201             if (newblock) {
10202                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10203             } else {
10204                 numtext[0] = NULLCHAR;
10205             }
10206         }
10207         numlen = strlen(numtext);
10208         newblock = FALSE;
10209
10210         /* Print move number */
10211         blank = linelen > 0 && numlen > 0;
10212         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10213             fprintf(f, "\n");
10214             linelen = 0;
10215             blank = 0;
10216         }
10217         if (blank) {
10218             fprintf(f, " ");
10219             linelen++;
10220         }
10221         fprintf(f, "%s", numtext);
10222         linelen += numlen;
10223
10224         /* Get move */
10225         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10226         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10227
10228         /* Print move */
10229         blank = linelen > 0 && movelen > 0;
10230         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10231             fprintf(f, "\n");
10232             linelen = 0;
10233             blank = 0;
10234         }
10235         if (blank) {
10236             fprintf(f, " ");
10237             linelen++;
10238         }
10239         fprintf(f, "%s", move_buffer);
10240         linelen += movelen;
10241
10242         /* [AS] Add PV info if present */
10243         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10244             /* [HGM] add time */
10245             char buf[MSG_SIZ]; int seconds;
10246
10247             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10248
10249             if( seconds <= 0) buf[0] = 0; else
10250             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10251                 seconds = (seconds + 4)/10; // round to full seconds
10252                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10253                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10254             }
10255
10256             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10257                 pvInfoList[i].score >= 0 ? "+" : "",
10258                 pvInfoList[i].score / 100.0,
10259                 pvInfoList[i].depth,
10260                 buf );
10261
10262             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10263
10264             /* Print score/depth */
10265             blank = linelen > 0 && movelen > 0;
10266             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10267                 fprintf(f, "\n");
10268                 linelen = 0;
10269                 blank = 0;
10270             }
10271             if (blank) {
10272                 fprintf(f, " ");
10273                 linelen++;
10274             }
10275             fprintf(f, "%s", move_buffer);
10276             linelen += movelen;
10277         }
10278
10279         i++;
10280     }
10281     
10282     /* Start a new line */
10283     if (linelen > 0) fprintf(f, "\n");
10284
10285     /* Print comments after last move */
10286     if (commentList[i] != NULL) {
10287         fprintf(f, "%s\n", commentList[i]);
10288     }
10289
10290     /* Print result */
10291     if (gameInfo.resultDetails != NULL &&
10292         gameInfo.resultDetails[0] != NULLCHAR) {
10293         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10294                 PGNResult(gameInfo.result));
10295     } else {
10296         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10297     }
10298
10299     fclose(f);
10300     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10301     return TRUE;
10302 }
10303
10304 /* Save game in old style and close the file */
10305 int
10306 SaveGameOldStyle(f)
10307      FILE *f;
10308 {
10309     int i, offset;
10310     time_t tm;
10311     
10312     tm = time((time_t *) NULL);
10313     
10314     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10315     PrintOpponents(f);
10316     
10317     if (backwardMostMove > 0 || startedFromSetupPosition) {
10318         fprintf(f, "\n[--------------\n");
10319         PrintPosition(f, backwardMostMove);
10320         fprintf(f, "--------------]\n");
10321     } else {
10322         fprintf(f, "\n");
10323     }
10324
10325     i = backwardMostMove;
10326     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10327
10328     while (i < forwardMostMove) {
10329         if (commentList[i] != NULL) {
10330             fprintf(f, "[%s]\n", commentList[i]);
10331         }
10332
10333         if ((i % 2) == 1) {
10334             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10335             i++;
10336         } else {
10337             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10338             i++;
10339             if (commentList[i] != NULL) {
10340                 fprintf(f, "\n");
10341                 continue;
10342             }
10343             if (i >= forwardMostMove) {
10344                 fprintf(f, "\n");
10345                 break;
10346             }
10347             fprintf(f, "%s\n", parseList[i]);
10348             i++;
10349         }
10350     }
10351     
10352     if (commentList[i] != NULL) {
10353         fprintf(f, "[%s]\n", commentList[i]);
10354     }
10355
10356     /* This isn't really the old style, but it's close enough */
10357     if (gameInfo.resultDetails != NULL &&
10358         gameInfo.resultDetails[0] != NULLCHAR) {
10359         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10360                 gameInfo.resultDetails);
10361     } else {
10362         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10363     }
10364
10365     fclose(f);
10366     return TRUE;
10367 }
10368
10369 /* Save the current game to open file f and close the file */
10370 int
10371 SaveGame(f, dummy, dummy2)
10372      FILE *f;
10373      int dummy;
10374      char *dummy2;
10375 {
10376     if (gameMode == EditPosition) EditPositionDone(TRUE);
10377     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10378     if (appData.oldSaveStyle)
10379       return SaveGameOldStyle(f);
10380     else
10381       return SaveGamePGN(f);
10382 }
10383
10384 /* Save the current position to the given file */
10385 int
10386 SavePositionToFile(filename)
10387      char *filename;
10388 {
10389     FILE *f;
10390     char buf[MSG_SIZ];
10391
10392     if (strcmp(filename, "-") == 0) {
10393         return SavePosition(stdout, 0, NULL);
10394     } else {
10395         f = fopen(filename, "a");
10396         if (f == NULL) {
10397             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10398             DisplayError(buf, errno);
10399             return FALSE;
10400         } else {
10401             SavePosition(f, 0, NULL);
10402             return TRUE;
10403         }
10404     }
10405 }
10406
10407 /* Save the current position to the given open file and close the file */
10408 int
10409 SavePosition(f, dummy, dummy2)
10410      FILE *f;
10411      int dummy;
10412      char *dummy2;
10413 {
10414     time_t tm;
10415     char *fen;
10416     
10417     if (gameMode == EditPosition) EditPositionDone(TRUE);
10418     if (appData.oldSaveStyle) {
10419         tm = time((time_t *) NULL);
10420     
10421         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10422         PrintOpponents(f);
10423         fprintf(f, "[--------------\n");
10424         PrintPosition(f, currentMove);
10425         fprintf(f, "--------------]\n");
10426     } else {
10427         fen = PositionToFEN(currentMove, NULL);
10428         fprintf(f, "%s\n", fen);
10429         free(fen);
10430     }
10431     fclose(f);
10432     return TRUE;
10433 }
10434
10435 void
10436 ReloadCmailMsgEvent(unregister)
10437      int unregister;
10438 {
10439 #if !WIN32
10440     static char *inFilename = NULL;
10441     static char *outFilename;
10442     int i;
10443     struct stat inbuf, outbuf;
10444     int status;
10445     
10446     /* Any registered moves are unregistered if unregister is set, */
10447     /* i.e. invoked by the signal handler */
10448     if (unregister) {
10449         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10450             cmailMoveRegistered[i] = FALSE;
10451             if (cmailCommentList[i] != NULL) {
10452                 free(cmailCommentList[i]);
10453                 cmailCommentList[i] = NULL;
10454             }
10455         }
10456         nCmailMovesRegistered = 0;
10457     }
10458
10459     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10460         cmailResult[i] = CMAIL_NOT_RESULT;
10461     }
10462     nCmailResults = 0;
10463
10464     if (inFilename == NULL) {
10465         /* Because the filenames are static they only get malloced once  */
10466         /* and they never get freed                                      */
10467         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10468         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10469
10470         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10471         sprintf(outFilename, "%s.out", appData.cmailGameName);
10472     }
10473     
10474     status = stat(outFilename, &outbuf);
10475     if (status < 0) {
10476         cmailMailedMove = FALSE;
10477     } else {
10478         status = stat(inFilename, &inbuf);
10479         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10480     }
10481     
10482     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10483        counts the games, notes how each one terminated, etc.
10484        
10485        It would be nice to remove this kludge and instead gather all
10486        the information while building the game list.  (And to keep it
10487        in the game list nodes instead of having a bunch of fixed-size
10488        parallel arrays.)  Note this will require getting each game's
10489        termination from the PGN tags, as the game list builder does
10490        not process the game moves.  --mann
10491        */
10492     cmailMsgLoaded = TRUE;
10493     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10494     
10495     /* Load first game in the file or popup game menu */
10496     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10497
10498 #endif /* !WIN32 */
10499     return;
10500 }
10501
10502 int
10503 RegisterMove()
10504 {
10505     FILE *f;
10506     char string[MSG_SIZ];
10507
10508     if (   cmailMailedMove
10509         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10510         return TRUE;            /* Allow free viewing  */
10511     }
10512
10513     /* Unregister move to ensure that we don't leave RegisterMove        */
10514     /* with the move registered when the conditions for registering no   */
10515     /* longer hold                                                       */
10516     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10517         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10518         nCmailMovesRegistered --;
10519
10520         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10521           {
10522               free(cmailCommentList[lastLoadGameNumber - 1]);
10523               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10524           }
10525     }
10526
10527     if (cmailOldMove == -1) {
10528         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10529         return FALSE;
10530     }
10531
10532     if (currentMove > cmailOldMove + 1) {
10533         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10534         return FALSE;
10535     }
10536
10537     if (currentMove < cmailOldMove) {
10538         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10539         return FALSE;
10540     }
10541
10542     if (forwardMostMove > currentMove) {
10543         /* Silently truncate extra moves */
10544         TruncateGame();
10545     }
10546
10547     if (   (currentMove == cmailOldMove + 1)
10548         || (   (currentMove == cmailOldMove)
10549             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10550                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10551         if (gameInfo.result != GameUnfinished) {
10552             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10553         }
10554
10555         if (commentList[currentMove] != NULL) {
10556             cmailCommentList[lastLoadGameNumber - 1]
10557               = StrSave(commentList[currentMove]);
10558         }
10559         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10560
10561         if (appData.debugMode)
10562           fprintf(debugFP, "Saving %s for game %d\n",
10563                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10564
10565         sprintf(string,
10566                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10567         
10568         f = fopen(string, "w");
10569         if (appData.oldSaveStyle) {
10570             SaveGameOldStyle(f); /* also closes the file */
10571             
10572             sprintf(string, "%s.pos.out", appData.cmailGameName);
10573             f = fopen(string, "w");
10574             SavePosition(f, 0, NULL); /* also closes the file */
10575         } else {
10576             fprintf(f, "{--------------\n");
10577             PrintPosition(f, currentMove);
10578             fprintf(f, "--------------}\n\n");
10579             
10580             SaveGame(f, 0, NULL); /* also closes the file*/
10581         }
10582         
10583         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10584         nCmailMovesRegistered ++;
10585     } else if (nCmailGames == 1) {
10586         DisplayError(_("You have not made a move yet"), 0);
10587         return FALSE;
10588     }
10589
10590     return TRUE;
10591 }
10592
10593 void
10594 MailMoveEvent()
10595 {
10596 #if !WIN32
10597     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10598     FILE *commandOutput;
10599     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10600     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10601     int nBuffers;
10602     int i;
10603     int archived;
10604     char *arcDir;
10605
10606     if (! cmailMsgLoaded) {
10607         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10608         return;
10609     }
10610
10611     if (nCmailGames == nCmailResults) {
10612         DisplayError(_("No unfinished games"), 0);
10613         return;
10614     }
10615
10616 #if CMAIL_PROHIBIT_REMAIL
10617     if (cmailMailedMove) {
10618         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);
10619         DisplayError(msg, 0);
10620         return;
10621     }
10622 #endif
10623
10624     if (! (cmailMailedMove || RegisterMove())) return;
10625     
10626     if (   cmailMailedMove
10627         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10628         sprintf(string, partCommandString,
10629                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10630         commandOutput = popen(string, "r");
10631
10632         if (commandOutput == NULL) {
10633             DisplayError(_("Failed to invoke cmail"), 0);
10634         } else {
10635             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10636                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10637             }
10638             if (nBuffers > 1) {
10639                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10640                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10641                 nBytes = MSG_SIZ - 1;
10642             } else {
10643                 (void) memcpy(msg, buffer, nBytes);
10644             }
10645             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10646
10647             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10648                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10649
10650                 archived = TRUE;
10651                 for (i = 0; i < nCmailGames; i ++) {
10652                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10653                         archived = FALSE;
10654                     }
10655                 }
10656                 if (   archived
10657                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10658                         != NULL)) {
10659                     sprintf(buffer, "%s/%s.%s.archive",
10660                             arcDir,
10661                             appData.cmailGameName,
10662                             gameInfo.date);
10663                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10664                     cmailMsgLoaded = FALSE;
10665                 }
10666             }
10667
10668             DisplayInformation(msg);
10669             pclose(commandOutput);
10670         }
10671     } else {
10672         if ((*cmailMsg) != '\0') {
10673             DisplayInformation(cmailMsg);
10674         }
10675     }
10676
10677     return;
10678 #endif /* !WIN32 */
10679 }
10680
10681 char *
10682 CmailMsg()
10683 {
10684 #if WIN32
10685     return NULL;
10686 #else
10687     int  prependComma = 0;
10688     char number[5];
10689     char string[MSG_SIZ];       /* Space for game-list */
10690     int  i;
10691     
10692     if (!cmailMsgLoaded) return "";
10693
10694     if (cmailMailedMove) {
10695         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10696     } else {
10697         /* Create a list of games left */
10698         sprintf(string, "[");
10699         for (i = 0; i < nCmailGames; i ++) {
10700             if (! (   cmailMoveRegistered[i]
10701                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10702                 if (prependComma) {
10703                     sprintf(number, ",%d", i + 1);
10704                 } else {
10705                     sprintf(number, "%d", i + 1);
10706                     prependComma = 1;
10707                 }
10708                 
10709                 strcat(string, number);
10710             }
10711         }
10712         strcat(string, "]");
10713
10714         if (nCmailMovesRegistered + nCmailResults == 0) {
10715             switch (nCmailGames) {
10716               case 1:
10717                 sprintf(cmailMsg,
10718                         _("Still need to make move for game\n"));
10719                 break;
10720                 
10721               case 2:
10722                 sprintf(cmailMsg,
10723                         _("Still need to make moves for both games\n"));
10724                 break;
10725                 
10726               default:
10727                 sprintf(cmailMsg,
10728                         _("Still need to make moves for all %d games\n"),
10729                         nCmailGames);
10730                 break;
10731             }
10732         } else {
10733             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10734               case 1:
10735                 sprintf(cmailMsg,
10736                         _("Still need to make a move for game %s\n"),
10737                         string);
10738                 break;
10739                 
10740               case 0:
10741                 if (nCmailResults == nCmailGames) {
10742                     sprintf(cmailMsg, _("No unfinished games\n"));
10743                 } else {
10744                     sprintf(cmailMsg, _("Ready to send mail\n"));
10745                 }
10746                 break;
10747                 
10748               default:
10749                 sprintf(cmailMsg,
10750                         _("Still need to make moves for games %s\n"),
10751                         string);
10752             }
10753         }
10754     }
10755     return cmailMsg;
10756 #endif /* WIN32 */
10757 }
10758
10759 void
10760 ResetGameEvent()
10761 {
10762     if (gameMode == Training)
10763       SetTrainingModeOff();
10764
10765     Reset(TRUE, TRUE);
10766     cmailMsgLoaded = FALSE;
10767     if (appData.icsActive) {
10768       SendToICS(ics_prefix);
10769       SendToICS("refresh\n");
10770     }
10771 }
10772
10773 void
10774 ExitEvent(status)
10775      int status;
10776 {
10777     exiting++;
10778     if (exiting > 2) {
10779       /* Give up on clean exit */
10780       exit(status);
10781     }
10782     if (exiting > 1) {
10783       /* Keep trying for clean exit */
10784       return;
10785     }
10786
10787     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10788
10789     if (telnetISR != NULL) {
10790       RemoveInputSource(telnetISR);
10791     }
10792     if (icsPR != NoProc) {
10793       DestroyChildProcess(icsPR, TRUE);
10794     }
10795
10796     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10797     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10798
10799     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10800     /* make sure this other one finishes before killing it!                  */
10801     if(endingGame) { int count = 0;
10802         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10803         while(endingGame && count++ < 10) DoSleep(1);
10804         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10805     }
10806
10807     /* Kill off chess programs */
10808     if (first.pr != NoProc) {
10809         ExitAnalyzeMode();
10810         
10811         DoSleep( appData.delayBeforeQuit );
10812         SendToProgram("quit\n", &first);
10813         DoSleep( appData.delayAfterQuit );
10814         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10815     }
10816     if (second.pr != NoProc) {
10817         DoSleep( appData.delayBeforeQuit );
10818         SendToProgram("quit\n", &second);
10819         DoSleep( appData.delayAfterQuit );
10820         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10821     }
10822     if (first.isr != NULL) {
10823         RemoveInputSource(first.isr);
10824     }
10825     if (second.isr != NULL) {
10826         RemoveInputSource(second.isr);
10827     }
10828
10829     ShutDownFrontEnd();
10830     exit(status);
10831 }
10832
10833 void
10834 PauseEvent()
10835 {
10836     if (appData.debugMode)
10837         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10838     if (pausing) {
10839         pausing = FALSE;
10840         ModeHighlight();
10841         if (gameMode == MachinePlaysWhite ||
10842             gameMode == MachinePlaysBlack) {
10843             StartClocks();
10844         } else {
10845             DisplayBothClocks();
10846         }
10847         if (gameMode == PlayFromGameFile) {
10848             if (appData.timeDelay >= 0) 
10849                 AutoPlayGameLoop();
10850         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10851             Reset(FALSE, TRUE);
10852             SendToICS(ics_prefix);
10853             SendToICS("refresh\n");
10854         } else if (currentMove < forwardMostMove) {
10855             ForwardInner(forwardMostMove);
10856         }
10857         pauseExamInvalid = FALSE;
10858     } else {
10859         switch (gameMode) {
10860           default:
10861             return;
10862           case IcsExamining:
10863             pauseExamForwardMostMove = forwardMostMove;
10864             pauseExamInvalid = FALSE;
10865             /* fall through */
10866           case IcsObserving:
10867           case IcsPlayingWhite:
10868           case IcsPlayingBlack:
10869             pausing = TRUE;
10870             ModeHighlight();
10871             return;
10872           case PlayFromGameFile:
10873             (void) StopLoadGameTimer();
10874             pausing = TRUE;
10875             ModeHighlight();
10876             break;
10877           case BeginningOfGame:
10878             if (appData.icsActive) return;
10879             /* else fall through */
10880           case MachinePlaysWhite:
10881           case MachinePlaysBlack:
10882           case TwoMachinesPlay:
10883             if (forwardMostMove == 0)
10884               return;           /* don't pause if no one has moved */
10885             if ((gameMode == MachinePlaysWhite &&
10886                  !WhiteOnMove(forwardMostMove)) ||
10887                 (gameMode == MachinePlaysBlack &&
10888                  WhiteOnMove(forwardMostMove))) {
10889                 StopClocks();
10890             }
10891             pausing = TRUE;
10892             ModeHighlight();
10893             break;
10894         }
10895     }
10896 }
10897
10898 void
10899 EditCommentEvent()
10900 {
10901     char title[MSG_SIZ];
10902
10903     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10904         strcpy(title, _("Edit comment"));
10905     } else {
10906         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10907                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10908                 parseList[currentMove - 1]);
10909     }
10910
10911     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10912 }
10913
10914
10915 void
10916 EditTagsEvent()
10917 {
10918     char *tags = PGNTags(&gameInfo);
10919     EditTagsPopUp(tags);
10920     free(tags);
10921 }
10922
10923 void
10924 AnalyzeModeEvent()
10925 {
10926     if (appData.noChessProgram || gameMode == AnalyzeMode)
10927       return;
10928
10929     if (gameMode != AnalyzeFile) {
10930         if (!appData.icsEngineAnalyze) {
10931                EditGameEvent();
10932                if (gameMode != EditGame) return;
10933         }
10934         ResurrectChessProgram();
10935         SendToProgram("analyze\n", &first);
10936         first.analyzing = TRUE;
10937         /*first.maybeThinking = TRUE;*/
10938         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10939         EngineOutputPopUp();
10940     }
10941     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10942     pausing = FALSE;
10943     ModeHighlight();
10944     SetGameInfo();
10945
10946     StartAnalysisClock();
10947     GetTimeMark(&lastNodeCountTime);
10948     lastNodeCount = 0;
10949 }
10950
10951 void
10952 AnalyzeFileEvent()
10953 {
10954     if (appData.noChessProgram || gameMode == AnalyzeFile)
10955       return;
10956
10957     if (gameMode != AnalyzeMode) {
10958         EditGameEvent();
10959         if (gameMode != EditGame) return;
10960         ResurrectChessProgram();
10961         SendToProgram("analyze\n", &first);
10962         first.analyzing = TRUE;
10963         /*first.maybeThinking = TRUE;*/
10964         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10965         EngineOutputPopUp();
10966     }
10967     gameMode = AnalyzeFile;
10968     pausing = FALSE;
10969     ModeHighlight();
10970     SetGameInfo();
10971
10972     StartAnalysisClock();
10973     GetTimeMark(&lastNodeCountTime);
10974     lastNodeCount = 0;
10975 }
10976
10977 void
10978 MachineWhiteEvent()
10979 {
10980     char buf[MSG_SIZ];
10981     char *bookHit = NULL;
10982
10983     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10984       return;
10985
10986
10987     if (gameMode == PlayFromGameFile || 
10988         gameMode == TwoMachinesPlay  || 
10989         gameMode == Training         || 
10990         gameMode == AnalyzeMode      || 
10991         gameMode == EndOfGame)
10992         EditGameEvent();
10993
10994     if (gameMode == EditPosition) 
10995         EditPositionDone(TRUE);
10996
10997     if (!WhiteOnMove(currentMove)) {
10998         DisplayError(_("It is not White's turn"), 0);
10999         return;
11000     }
11001   
11002     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11003       ExitAnalyzeMode();
11004
11005     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11006         gameMode == AnalyzeFile)
11007         TruncateGame();
11008
11009     ResurrectChessProgram();    /* in case it isn't running */
11010     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11011         gameMode = MachinePlaysWhite;
11012         ResetClocks();
11013     } else
11014     gameMode = MachinePlaysWhite;
11015     pausing = FALSE;
11016     ModeHighlight();
11017     SetGameInfo();
11018     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11019     DisplayTitle(buf);
11020     if (first.sendName) {
11021       sprintf(buf, "name %s\n", gameInfo.black);
11022       SendToProgram(buf, &first);
11023     }
11024     if (first.sendTime) {
11025       if (first.useColors) {
11026         SendToProgram("black\n", &first); /*gnu kludge*/
11027       }
11028       SendTimeRemaining(&first, TRUE);
11029     }
11030     if (first.useColors) {
11031       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11032     }
11033     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11034     SetMachineThinkingEnables();
11035     first.maybeThinking = TRUE;
11036     StartClocks();
11037     firstMove = FALSE;
11038
11039     if (appData.autoFlipView && !flipView) {
11040       flipView = !flipView;
11041       DrawPosition(FALSE, NULL);
11042       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11043     }
11044
11045     if(bookHit) { // [HGM] book: simulate book reply
11046         static char bookMove[MSG_SIZ]; // a bit generous?
11047
11048         programStats.nodes = programStats.depth = programStats.time = 
11049         programStats.score = programStats.got_only_move = 0;
11050         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11051
11052         strcpy(bookMove, "move ");
11053         strcat(bookMove, bookHit);
11054         HandleMachineMove(bookMove, &first);
11055     }
11056 }
11057
11058 void
11059 MachineBlackEvent()
11060 {
11061     char buf[MSG_SIZ];
11062    char *bookHit = NULL;
11063
11064     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11065         return;
11066
11067
11068     if (gameMode == PlayFromGameFile || 
11069         gameMode == TwoMachinesPlay  || 
11070         gameMode == Training         || 
11071         gameMode == AnalyzeMode      || 
11072         gameMode == EndOfGame)
11073         EditGameEvent();
11074
11075     if (gameMode == EditPosition) 
11076         EditPositionDone(TRUE);
11077
11078     if (WhiteOnMove(currentMove)) {
11079         DisplayError(_("It is not Black's turn"), 0);
11080         return;
11081     }
11082     
11083     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11084       ExitAnalyzeMode();
11085
11086     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11087         gameMode == AnalyzeFile)
11088         TruncateGame();
11089
11090     ResurrectChessProgram();    /* in case it isn't running */
11091     gameMode = MachinePlaysBlack;
11092     pausing = FALSE;
11093     ModeHighlight();
11094     SetGameInfo();
11095     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11096     DisplayTitle(buf);
11097     if (first.sendName) {
11098       sprintf(buf, "name %s\n", gameInfo.white);
11099       SendToProgram(buf, &first);
11100     }
11101     if (first.sendTime) {
11102       if (first.useColors) {
11103         SendToProgram("white\n", &first); /*gnu kludge*/
11104       }
11105       SendTimeRemaining(&first, FALSE);
11106     }
11107     if (first.useColors) {
11108       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11109     }
11110     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11111     SetMachineThinkingEnables();
11112     first.maybeThinking = TRUE;
11113     StartClocks();
11114
11115     if (appData.autoFlipView && flipView) {
11116       flipView = !flipView;
11117       DrawPosition(FALSE, NULL);
11118       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11119     }
11120     if(bookHit) { // [HGM] book: simulate book reply
11121         static char bookMove[MSG_SIZ]; // a bit generous?
11122
11123         programStats.nodes = programStats.depth = programStats.time = 
11124         programStats.score = programStats.got_only_move = 0;
11125         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11126
11127         strcpy(bookMove, "move ");
11128         strcat(bookMove, bookHit);
11129         HandleMachineMove(bookMove, &first);
11130     }
11131 }
11132
11133
11134 void
11135 DisplayTwoMachinesTitle()
11136 {
11137     char buf[MSG_SIZ];
11138     if (appData.matchGames > 0) {
11139         if (first.twoMachinesColor[0] == 'w') {
11140             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11141                     gameInfo.white, gameInfo.black,
11142                     first.matchWins, second.matchWins,
11143                     matchGame - 1 - (first.matchWins + second.matchWins));
11144         } else {
11145             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11146                     gameInfo.white, gameInfo.black,
11147                     second.matchWins, first.matchWins,
11148                     matchGame - 1 - (first.matchWins + second.matchWins));
11149         }
11150     } else {
11151         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11152     }
11153     DisplayTitle(buf);
11154 }
11155
11156 void
11157 TwoMachinesEvent P((void))
11158 {
11159     int i;
11160     char buf[MSG_SIZ];
11161     ChessProgramState *onmove;
11162     char *bookHit = NULL;
11163     
11164     if (appData.noChessProgram) return;
11165
11166     switch (gameMode) {
11167       case TwoMachinesPlay:
11168         return;
11169       case MachinePlaysWhite:
11170       case MachinePlaysBlack:
11171         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11172             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11173             return;
11174         }
11175         /* fall through */
11176       case BeginningOfGame:
11177       case PlayFromGameFile:
11178       case EndOfGame:
11179         EditGameEvent();
11180         if (gameMode != EditGame) return;
11181         break;
11182       case EditPosition:
11183         EditPositionDone(TRUE);
11184         break;
11185       case AnalyzeMode:
11186       case AnalyzeFile:
11187         ExitAnalyzeMode();
11188         break;
11189       case EditGame:
11190       default:
11191         break;
11192     }
11193
11194 //    forwardMostMove = currentMove;
11195     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11196     ResurrectChessProgram();    /* in case first program isn't running */
11197
11198     if (second.pr == NULL) {
11199         StartChessProgram(&second);
11200         if (second.protocolVersion == 1) {
11201           TwoMachinesEventIfReady();
11202         } else {
11203           /* kludge: allow timeout for initial "feature" command */
11204           FreezeUI();
11205           DisplayMessage("", _("Starting second chess program"));
11206           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11207         }
11208         return;
11209     }
11210     DisplayMessage("", "");
11211     InitChessProgram(&second, FALSE);
11212     SendToProgram("force\n", &second);
11213     if (startedFromSetupPosition) {
11214         SendBoard(&second, backwardMostMove);
11215     if (appData.debugMode) {
11216         fprintf(debugFP, "Two Machines\n");
11217     }
11218     }
11219     for (i = backwardMostMove; i < forwardMostMove; i++) {
11220         SendMoveToProgram(i, &second);
11221     }
11222
11223     gameMode = TwoMachinesPlay;
11224     pausing = FALSE;
11225     ModeHighlight();
11226     SetGameInfo();
11227     DisplayTwoMachinesTitle();
11228     firstMove = TRUE;
11229     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11230         onmove = &first;
11231     } else {
11232         onmove = &second;
11233     }
11234
11235     SendToProgram(first.computerString, &first);
11236     if (first.sendName) {
11237       sprintf(buf, "name %s\n", second.tidy);
11238       SendToProgram(buf, &first);
11239     }
11240     SendToProgram(second.computerString, &second);
11241     if (second.sendName) {
11242       sprintf(buf, "name %s\n", first.tidy);
11243       SendToProgram(buf, &second);
11244     }
11245
11246     ResetClocks();
11247     if (!first.sendTime || !second.sendTime) {
11248         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11249         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11250     }
11251     if (onmove->sendTime) {
11252       if (onmove->useColors) {
11253         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11254       }
11255       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11256     }
11257     if (onmove->useColors) {
11258       SendToProgram(onmove->twoMachinesColor, onmove);
11259     }
11260     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11261 //    SendToProgram("go\n", onmove);
11262     onmove->maybeThinking = TRUE;
11263     SetMachineThinkingEnables();
11264
11265     StartClocks();
11266
11267     if(bookHit) { // [HGM] book: simulate book reply
11268         static char bookMove[MSG_SIZ]; // a bit generous?
11269
11270         programStats.nodes = programStats.depth = programStats.time = 
11271         programStats.score = programStats.got_only_move = 0;
11272         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11273
11274         strcpy(bookMove, "move ");
11275         strcat(bookMove, bookHit);
11276         savedMessage = bookMove; // args for deferred call
11277         savedState = onmove;
11278         ScheduleDelayedEvent(DeferredBookMove, 1);
11279     }
11280 }
11281
11282 void
11283 TrainingEvent()
11284 {
11285     if (gameMode == Training) {
11286       SetTrainingModeOff();
11287       gameMode = PlayFromGameFile;
11288       DisplayMessage("", _("Training mode off"));
11289     } else {
11290       gameMode = Training;
11291       animateTraining = appData.animate;
11292
11293       /* make sure we are not already at the end of the game */
11294       if (currentMove < forwardMostMove) {
11295         SetTrainingModeOn();
11296         DisplayMessage("", _("Training mode on"));
11297       } else {
11298         gameMode = PlayFromGameFile;
11299         DisplayError(_("Already at end of game"), 0);
11300       }
11301     }
11302     ModeHighlight();
11303 }
11304
11305 void
11306 IcsClientEvent()
11307 {
11308     if (!appData.icsActive) return;
11309     switch (gameMode) {
11310       case IcsPlayingWhite:
11311       case IcsPlayingBlack:
11312       case IcsObserving:
11313       case IcsIdle:
11314       case BeginningOfGame:
11315       case IcsExamining:
11316         return;
11317
11318       case EditGame:
11319         break;
11320
11321       case EditPosition:
11322         EditPositionDone(TRUE);
11323         break;
11324
11325       case AnalyzeMode:
11326       case AnalyzeFile:
11327         ExitAnalyzeMode();
11328         break;
11329         
11330       default:
11331         EditGameEvent();
11332         break;
11333     }
11334
11335     gameMode = IcsIdle;
11336     ModeHighlight();
11337     return;
11338 }
11339
11340
11341 void
11342 EditGameEvent()
11343 {
11344     int i;
11345
11346     switch (gameMode) {
11347       case Training:
11348         SetTrainingModeOff();
11349         break;
11350       case MachinePlaysWhite:
11351       case MachinePlaysBlack:
11352       case BeginningOfGame:
11353         SendToProgram("force\n", &first);
11354         SetUserThinkingEnables();
11355         break;
11356       case PlayFromGameFile:
11357         (void) StopLoadGameTimer();
11358         if (gameFileFP != NULL) {
11359             gameFileFP = NULL;
11360         }
11361         break;
11362       case EditPosition:
11363         EditPositionDone(TRUE);
11364         break;
11365       case AnalyzeMode:
11366       case AnalyzeFile:
11367         ExitAnalyzeMode();
11368         SendToProgram("force\n", &first);
11369         break;
11370       case TwoMachinesPlay:
11371         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11372         ResurrectChessProgram();
11373         SetUserThinkingEnables();
11374         break;
11375       case EndOfGame:
11376         ResurrectChessProgram();
11377         break;
11378       case IcsPlayingBlack:
11379       case IcsPlayingWhite:
11380         DisplayError(_("Warning: You are still playing a game"), 0);
11381         break;
11382       case IcsObserving:
11383         DisplayError(_("Warning: You are still observing a game"), 0);
11384         break;
11385       case IcsExamining:
11386         DisplayError(_("Warning: You are still examining a game"), 0);
11387         break;
11388       case IcsIdle:
11389         break;
11390       case EditGame:
11391       default:
11392         return;
11393     }
11394     
11395     pausing = FALSE;
11396     StopClocks();
11397     first.offeredDraw = second.offeredDraw = 0;
11398
11399     if (gameMode == PlayFromGameFile) {
11400         whiteTimeRemaining = timeRemaining[0][currentMove];
11401         blackTimeRemaining = timeRemaining[1][currentMove];
11402         DisplayTitle("");
11403     }
11404
11405     if (gameMode == MachinePlaysWhite ||
11406         gameMode == MachinePlaysBlack ||
11407         gameMode == TwoMachinesPlay ||
11408         gameMode == EndOfGame) {
11409         i = forwardMostMove;
11410         while (i > currentMove) {
11411             SendToProgram("undo\n", &first);
11412             i--;
11413         }
11414         whiteTimeRemaining = timeRemaining[0][currentMove];
11415         blackTimeRemaining = timeRemaining[1][currentMove];
11416         DisplayBothClocks();
11417         if (whiteFlag || blackFlag) {
11418             whiteFlag = blackFlag = 0;
11419         }
11420         DisplayTitle("");
11421     }           
11422     
11423     gameMode = EditGame;
11424     ModeHighlight();
11425     SetGameInfo();
11426 }
11427
11428
11429 void
11430 EditPositionEvent()
11431 {
11432     if (gameMode == EditPosition) {
11433         EditGameEvent();
11434         return;
11435     }
11436     
11437     EditGameEvent();
11438     if (gameMode != EditGame) return;
11439     
11440     gameMode = EditPosition;
11441     ModeHighlight();
11442     SetGameInfo();
11443     if (currentMove > 0)
11444       CopyBoard(boards[0], boards[currentMove]);
11445     
11446     blackPlaysFirst = !WhiteOnMove(currentMove);
11447     ResetClocks();
11448     currentMove = forwardMostMove = backwardMostMove = 0;
11449     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11450     DisplayMove(-1);
11451 }
11452
11453 void
11454 ExitAnalyzeMode()
11455 {
11456     /* [DM] icsEngineAnalyze - possible call from other functions */
11457     if (appData.icsEngineAnalyze) {
11458         appData.icsEngineAnalyze = FALSE;
11459
11460         DisplayMessage("",_("Close ICS engine analyze..."));
11461     }
11462     if (first.analysisSupport && first.analyzing) {
11463       SendToProgram("exit\n", &first);
11464       first.analyzing = FALSE;
11465     }
11466     thinkOutput[0] = NULLCHAR;
11467 }
11468
11469 void
11470 EditPositionDone(Boolean fakeRights)
11471 {
11472     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11473
11474     startedFromSetupPosition = TRUE;
11475     InitChessProgram(&first, FALSE);
11476     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11477       boards[0][EP_STATUS] = EP_NONE;
11478       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11479     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11480         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11481         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11482       } else boards[0][CASTLING][2] = NoRights;
11483     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11484         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11485         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11486       } else boards[0][CASTLING][5] = NoRights;
11487     }
11488     SendToProgram("force\n", &first);
11489     if (blackPlaysFirst) {
11490         strcpy(moveList[0], "");
11491         strcpy(parseList[0], "");
11492         currentMove = forwardMostMove = backwardMostMove = 1;
11493         CopyBoard(boards[1], boards[0]);
11494     } else {
11495         currentMove = forwardMostMove = backwardMostMove = 0;
11496     }
11497     SendBoard(&first, forwardMostMove);
11498     if (appData.debugMode) {
11499         fprintf(debugFP, "EditPosDone\n");
11500     }
11501     DisplayTitle("");
11502     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11503     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11504     gameMode = EditGame;
11505     ModeHighlight();
11506     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11507     ClearHighlights(); /* [AS] */
11508 }
11509
11510 /* Pause for `ms' milliseconds */
11511 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11512 void
11513 TimeDelay(ms)
11514      long ms;
11515 {
11516     TimeMark m1, m2;
11517
11518     GetTimeMark(&m1);
11519     do {
11520         GetTimeMark(&m2);
11521     } while (SubtractTimeMarks(&m2, &m1) < ms);
11522 }
11523
11524 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11525 void
11526 SendMultiLineToICS(buf)
11527      char *buf;
11528 {
11529     char temp[MSG_SIZ+1], *p;
11530     int len;
11531
11532     len = strlen(buf);
11533     if (len > MSG_SIZ)
11534       len = MSG_SIZ;
11535   
11536     strncpy(temp, buf, len);
11537     temp[len] = 0;
11538
11539     p = temp;
11540     while (*p) {
11541         if (*p == '\n' || *p == '\r')
11542           *p = ' ';
11543         ++p;
11544     }
11545
11546     strcat(temp, "\n");
11547     SendToICS(temp);
11548     SendToPlayer(temp, strlen(temp));
11549 }
11550
11551 void
11552 SetWhiteToPlayEvent()
11553 {
11554     if (gameMode == EditPosition) {
11555         blackPlaysFirst = FALSE;
11556         DisplayBothClocks();    /* works because currentMove is 0 */
11557     } else if (gameMode == IcsExamining) {
11558         SendToICS(ics_prefix);
11559         SendToICS("tomove white\n");
11560     }
11561 }
11562
11563 void
11564 SetBlackToPlayEvent()
11565 {
11566     if (gameMode == EditPosition) {
11567         blackPlaysFirst = TRUE;
11568         currentMove = 1;        /* kludge */
11569         DisplayBothClocks();
11570         currentMove = 0;
11571     } else if (gameMode == IcsExamining) {
11572         SendToICS(ics_prefix);
11573         SendToICS("tomove black\n");
11574     }
11575 }
11576
11577 void
11578 EditPositionMenuEvent(selection, x, y)
11579      ChessSquare selection;
11580      int x, y;
11581 {
11582     char buf[MSG_SIZ];
11583     ChessSquare piece = boards[0][y][x];
11584
11585     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11586
11587     switch (selection) {
11588       case ClearBoard:
11589         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11590             SendToICS(ics_prefix);
11591             SendToICS("bsetup clear\n");
11592         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11593             SendToICS(ics_prefix);
11594             SendToICS("clearboard\n");
11595         } else {
11596             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11597                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11598                 for (y = 0; y < BOARD_HEIGHT; y++) {
11599                     if (gameMode == IcsExamining) {
11600                         if (boards[currentMove][y][x] != EmptySquare) {
11601                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11602                                     AAA + x, ONE + y);
11603                             SendToICS(buf);
11604                         }
11605                     } else {
11606                         boards[0][y][x] = p;
11607                     }
11608                 }
11609             }
11610         }
11611         if (gameMode == EditPosition) {
11612             DrawPosition(FALSE, boards[0]);
11613         }
11614         break;
11615
11616       case WhitePlay:
11617         SetWhiteToPlayEvent();
11618         break;
11619
11620       case BlackPlay:
11621         SetBlackToPlayEvent();
11622         break;
11623
11624       case EmptySquare:
11625         if (gameMode == IcsExamining) {
11626             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11627             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11628             SendToICS(buf);
11629         } else {
11630             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11631                 if(x == BOARD_LEFT-2) {
11632                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11633                     boards[0][y][1] = 0;
11634                 } else
11635                 if(x == BOARD_RGHT+1) {
11636                     if(y >= gameInfo.holdingsSize) break;
11637                     boards[0][y][BOARD_WIDTH-2] = 0;
11638                 } else break;
11639             }
11640             boards[0][y][x] = EmptySquare;
11641             DrawPosition(FALSE, boards[0]);
11642         }
11643         break;
11644
11645       case PromotePiece:
11646         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11647            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11648             selection = (ChessSquare) (PROMOTED piece);
11649         } else if(piece == EmptySquare) selection = WhiteSilver;
11650         else selection = (ChessSquare)((int)piece - 1);
11651         goto defaultlabel;
11652
11653       case DemotePiece:
11654         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11655            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11656             selection = (ChessSquare) (DEMOTED piece);
11657         } else if(piece == EmptySquare) selection = BlackSilver;
11658         else selection = (ChessSquare)((int)piece + 1);       
11659         goto defaultlabel;
11660
11661       case WhiteQueen:
11662       case BlackQueen:
11663         if(gameInfo.variant == VariantShatranj ||
11664            gameInfo.variant == VariantXiangqi  ||
11665            gameInfo.variant == VariantCourier  ||
11666            gameInfo.variant == VariantMakruk     )
11667             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11668         goto defaultlabel;
11669
11670       case WhiteKing:
11671       case BlackKing:
11672         if(gameInfo.variant == VariantXiangqi)
11673             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11674         if(gameInfo.variant == VariantKnightmate)
11675             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11676       default:
11677         defaultlabel:
11678         if (gameMode == IcsExamining) {
11679             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11680             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11681                     PieceToChar(selection), AAA + x, ONE + y);
11682             SendToICS(buf);
11683         } else {
11684             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11685                 int n;
11686                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11687                     n = PieceToNumber(selection - BlackPawn);
11688                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11689                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11690                     boards[0][BOARD_HEIGHT-1-n][1]++;
11691                 } else
11692                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11693                     n = PieceToNumber(selection);
11694                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11695                     boards[0][n][BOARD_WIDTH-1] = selection;
11696                     boards[0][n][BOARD_WIDTH-2]++;
11697                 }
11698             } else
11699             boards[0][y][x] = selection;
11700             DrawPosition(TRUE, boards[0]);
11701         }
11702         break;
11703     }
11704 }
11705
11706
11707 void
11708 DropMenuEvent(selection, x, y)
11709      ChessSquare selection;
11710      int x, y;
11711 {
11712     ChessMove moveType;
11713
11714     switch (gameMode) {
11715       case IcsPlayingWhite:
11716       case MachinePlaysBlack:
11717         if (!WhiteOnMove(currentMove)) {
11718             DisplayMoveError(_("It is Black's turn"));
11719             return;
11720         }
11721         moveType = WhiteDrop;
11722         break;
11723       case IcsPlayingBlack:
11724       case MachinePlaysWhite:
11725         if (WhiteOnMove(currentMove)) {
11726             DisplayMoveError(_("It is White's turn"));
11727             return;
11728         }
11729         moveType = BlackDrop;
11730         break;
11731       case EditGame:
11732         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11733         break;
11734       default:
11735         return;
11736     }
11737
11738     if (moveType == BlackDrop && selection < BlackPawn) {
11739       selection = (ChessSquare) ((int) selection
11740                                  + (int) BlackPawn - (int) WhitePawn);
11741     }
11742     if (boards[currentMove][y][x] != EmptySquare) {
11743         DisplayMoveError(_("That square is occupied"));
11744         return;
11745     }
11746
11747     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11748 }
11749
11750 void
11751 AcceptEvent()
11752 {
11753     /* Accept a pending offer of any kind from opponent */
11754     
11755     if (appData.icsActive) {
11756         SendToICS(ics_prefix);
11757         SendToICS("accept\n");
11758     } else if (cmailMsgLoaded) {
11759         if (currentMove == cmailOldMove &&
11760             commentList[cmailOldMove] != NULL &&
11761             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11762                    "Black offers a draw" : "White offers a draw")) {
11763             TruncateGame();
11764             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11765             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11766         } else {
11767             DisplayError(_("There is no pending offer on this move"), 0);
11768             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11769         }
11770     } else {
11771         /* Not used for offers from chess program */
11772     }
11773 }
11774
11775 void
11776 DeclineEvent()
11777 {
11778     /* Decline a pending offer of any kind from opponent */
11779     
11780     if (appData.icsActive) {
11781         SendToICS(ics_prefix);
11782         SendToICS("decline\n");
11783     } else if (cmailMsgLoaded) {
11784         if (currentMove == cmailOldMove &&
11785             commentList[cmailOldMove] != NULL &&
11786             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11787                    "Black offers a draw" : "White offers a draw")) {
11788 #ifdef NOTDEF
11789             AppendComment(cmailOldMove, "Draw declined", TRUE);
11790             DisplayComment(cmailOldMove - 1, "Draw declined");
11791 #endif /*NOTDEF*/
11792         } else {
11793             DisplayError(_("There is no pending offer on this move"), 0);
11794         }
11795     } else {
11796         /* Not used for offers from chess program */
11797     }
11798 }
11799
11800 void
11801 RematchEvent()
11802 {
11803     /* Issue ICS rematch command */
11804     if (appData.icsActive) {
11805         SendToICS(ics_prefix);
11806         SendToICS("rematch\n");
11807     }
11808 }
11809
11810 void
11811 CallFlagEvent()
11812 {
11813     /* Call your opponent's flag (claim a win on time) */
11814     if (appData.icsActive) {
11815         SendToICS(ics_prefix);
11816         SendToICS("flag\n");
11817     } else {
11818         switch (gameMode) {
11819           default:
11820             return;
11821           case MachinePlaysWhite:
11822             if (whiteFlag) {
11823                 if (blackFlag)
11824                   GameEnds(GameIsDrawn, "Both players ran out of time",
11825                            GE_PLAYER);
11826                 else
11827                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11828             } else {
11829                 DisplayError(_("Your opponent is not out of time"), 0);
11830             }
11831             break;
11832           case MachinePlaysBlack:
11833             if (blackFlag) {
11834                 if (whiteFlag)
11835                   GameEnds(GameIsDrawn, "Both players ran out of time",
11836                            GE_PLAYER);
11837                 else
11838                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11839             } else {
11840                 DisplayError(_("Your opponent is not out of time"), 0);
11841             }
11842             break;
11843         }
11844     }
11845 }
11846
11847 void
11848 DrawEvent()
11849 {
11850     /* Offer draw or accept pending draw offer from opponent */
11851     
11852     if (appData.icsActive) {
11853         /* Note: tournament rules require draw offers to be
11854            made after you make your move but before you punch
11855            your clock.  Currently ICS doesn't let you do that;
11856            instead, you immediately punch your clock after making
11857            a move, but you can offer a draw at any time. */
11858         
11859         SendToICS(ics_prefix);
11860         SendToICS("draw\n");
11861         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11862     } else if (cmailMsgLoaded) {
11863         if (currentMove == cmailOldMove &&
11864             commentList[cmailOldMove] != NULL &&
11865             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11866                    "Black offers a draw" : "White offers a draw")) {
11867             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11868             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11869         } else if (currentMove == cmailOldMove + 1) {
11870             char *offer = WhiteOnMove(cmailOldMove) ?
11871               "White offers a draw" : "Black offers a draw";
11872             AppendComment(currentMove, offer, TRUE);
11873             DisplayComment(currentMove - 1, offer);
11874             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11875         } else {
11876             DisplayError(_("You must make your move before offering a draw"), 0);
11877             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11878         }
11879     } else if (first.offeredDraw) {
11880         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11881     } else {
11882         if (first.sendDrawOffers) {
11883             SendToProgram("draw\n", &first);
11884             userOfferedDraw = TRUE;
11885         }
11886     }
11887 }
11888
11889 void
11890 AdjournEvent()
11891 {
11892     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11893     
11894     if (appData.icsActive) {
11895         SendToICS(ics_prefix);
11896         SendToICS("adjourn\n");
11897     } else {
11898         /* Currently GNU Chess doesn't offer or accept Adjourns */
11899     }
11900 }
11901
11902
11903 void
11904 AbortEvent()
11905 {
11906     /* Offer Abort or accept pending Abort offer from opponent */
11907     
11908     if (appData.icsActive) {
11909         SendToICS(ics_prefix);
11910         SendToICS("abort\n");
11911     } else {
11912         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11913     }
11914 }
11915
11916 void
11917 ResignEvent()
11918 {
11919     /* Resign.  You can do this even if it's not your turn. */
11920     
11921     if (appData.icsActive) {
11922         SendToICS(ics_prefix);
11923         SendToICS("resign\n");
11924     } else {
11925         switch (gameMode) {
11926           case MachinePlaysWhite:
11927             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11928             break;
11929           case MachinePlaysBlack:
11930             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11931             break;
11932           case EditGame:
11933             if (cmailMsgLoaded) {
11934                 TruncateGame();
11935                 if (WhiteOnMove(cmailOldMove)) {
11936                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11937                 } else {
11938                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11939                 }
11940                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11941             }
11942             break;
11943           default:
11944             break;
11945         }
11946     }
11947 }
11948
11949
11950 void
11951 StopObservingEvent()
11952 {
11953     /* Stop observing current games */
11954     SendToICS(ics_prefix);
11955     SendToICS("unobserve\n");
11956 }
11957
11958 void
11959 StopExaminingEvent()
11960 {
11961     /* Stop observing current game */
11962     SendToICS(ics_prefix);
11963     SendToICS("unexamine\n");
11964 }
11965
11966 void
11967 ForwardInner(target)
11968      int target;
11969 {
11970     int limit;
11971
11972     if (appData.debugMode)
11973         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11974                 target, currentMove, forwardMostMove);
11975
11976     if (gameMode == EditPosition)
11977       return;
11978
11979     if (gameMode == PlayFromGameFile && !pausing)
11980       PauseEvent();
11981     
11982     if (gameMode == IcsExamining && pausing)
11983       limit = pauseExamForwardMostMove;
11984     else
11985       limit = forwardMostMove;
11986     
11987     if (target > limit) target = limit;
11988
11989     if (target > 0 && moveList[target - 1][0]) {
11990         int fromX, fromY, toX, toY;
11991         toX = moveList[target - 1][2] - AAA;
11992         toY = moveList[target - 1][3] - ONE;
11993         if (moveList[target - 1][1] == '@') {
11994             if (appData.highlightLastMove) {
11995                 SetHighlights(-1, -1, toX, toY);
11996             }
11997         } else {
11998             fromX = moveList[target - 1][0] - AAA;
11999             fromY = moveList[target - 1][1] - ONE;
12000             if (target == currentMove + 1) {
12001                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12002             }
12003             if (appData.highlightLastMove) {
12004                 SetHighlights(fromX, fromY, toX, toY);
12005             }
12006         }
12007     }
12008     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12009         gameMode == Training || gameMode == PlayFromGameFile || 
12010         gameMode == AnalyzeFile) {
12011         while (currentMove < target) {
12012             SendMoveToProgram(currentMove++, &first);
12013         }
12014     } else {
12015         currentMove = target;
12016     }
12017     
12018     if (gameMode == EditGame || gameMode == EndOfGame) {
12019         whiteTimeRemaining = timeRemaining[0][currentMove];
12020         blackTimeRemaining = timeRemaining[1][currentMove];
12021     }
12022     DisplayBothClocks();
12023     DisplayMove(currentMove - 1);
12024     DrawPosition(FALSE, boards[currentMove]);
12025     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12026     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12027         DisplayComment(currentMove - 1, commentList[currentMove]);
12028     }
12029 }
12030
12031
12032 void
12033 ForwardEvent()
12034 {
12035     if (gameMode == IcsExamining && !pausing) {
12036         SendToICS(ics_prefix);
12037         SendToICS("forward\n");
12038     } else {
12039         ForwardInner(currentMove + 1);
12040     }
12041 }
12042
12043 void
12044 ToEndEvent()
12045 {
12046     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12047         /* to optimze, we temporarily turn off analysis mode while we feed
12048          * the remaining moves to the engine. Otherwise we get analysis output
12049          * after each move.
12050          */ 
12051         if (first.analysisSupport) {
12052           SendToProgram("exit\nforce\n", &first);
12053           first.analyzing = FALSE;
12054         }
12055     }
12056         
12057     if (gameMode == IcsExamining && !pausing) {
12058         SendToICS(ics_prefix);
12059         SendToICS("forward 999999\n");
12060     } else {
12061         ForwardInner(forwardMostMove);
12062     }
12063
12064     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12065         /* we have fed all the moves, so reactivate analysis mode */
12066         SendToProgram("analyze\n", &first);
12067         first.analyzing = TRUE;
12068         /*first.maybeThinking = TRUE;*/
12069         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12070     }
12071 }
12072
12073 void
12074 BackwardInner(target)
12075      int target;
12076 {
12077     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12078
12079     if (appData.debugMode)
12080         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12081                 target, currentMove, forwardMostMove);
12082
12083     if (gameMode == EditPosition) return;
12084     if (currentMove <= backwardMostMove) {
12085         ClearHighlights();
12086         DrawPosition(full_redraw, boards[currentMove]);
12087         return;
12088     }
12089     if (gameMode == PlayFromGameFile && !pausing)
12090       PauseEvent();
12091     
12092     if (moveList[target][0]) {
12093         int fromX, fromY, toX, toY;
12094         toX = moveList[target][2] - AAA;
12095         toY = moveList[target][3] - ONE;
12096         if (moveList[target][1] == '@') {
12097             if (appData.highlightLastMove) {
12098                 SetHighlights(-1, -1, toX, toY);
12099             }
12100         } else {
12101             fromX = moveList[target][0] - AAA;
12102             fromY = moveList[target][1] - ONE;
12103             if (target == currentMove - 1) {
12104                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12105             }
12106             if (appData.highlightLastMove) {
12107                 SetHighlights(fromX, fromY, toX, toY);
12108             }
12109         }
12110     }
12111     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12112         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12113         while (currentMove > target) {
12114             SendToProgram("undo\n", &first);
12115             currentMove--;
12116         }
12117     } else {
12118         currentMove = target;
12119     }
12120     
12121     if (gameMode == EditGame || gameMode == EndOfGame) {
12122         whiteTimeRemaining = timeRemaining[0][currentMove];
12123         blackTimeRemaining = timeRemaining[1][currentMove];
12124     }
12125     DisplayBothClocks();
12126     DisplayMove(currentMove - 1);
12127     DrawPosition(full_redraw, boards[currentMove]);
12128     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12129     // [HGM] PV info: routine tests if comment empty
12130     DisplayComment(currentMove - 1, commentList[currentMove]);
12131 }
12132
12133 void
12134 BackwardEvent()
12135 {
12136     if (gameMode == IcsExamining && !pausing) {
12137         SendToICS(ics_prefix);
12138         SendToICS("backward\n");
12139     } else {
12140         BackwardInner(currentMove - 1);
12141     }
12142 }
12143
12144 void
12145 ToStartEvent()
12146 {
12147     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12148         /* to optimize, we temporarily turn off analysis mode while we undo
12149          * all the moves. Otherwise we get analysis output after each undo.
12150          */ 
12151         if (first.analysisSupport) {
12152           SendToProgram("exit\nforce\n", &first);
12153           first.analyzing = FALSE;
12154         }
12155     }
12156
12157     if (gameMode == IcsExamining && !pausing) {
12158         SendToICS(ics_prefix);
12159         SendToICS("backward 999999\n");
12160     } else {
12161         BackwardInner(backwardMostMove);
12162     }
12163
12164     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12165         /* we have fed all the moves, so reactivate analysis mode */
12166         SendToProgram("analyze\n", &first);
12167         first.analyzing = TRUE;
12168         /*first.maybeThinking = TRUE;*/
12169         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12170     }
12171 }
12172
12173 void
12174 ToNrEvent(int to)
12175 {
12176   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12177   if (to >= forwardMostMove) to = forwardMostMove;
12178   if (to <= backwardMostMove) to = backwardMostMove;
12179   if (to < currentMove) {
12180     BackwardInner(to);
12181   } else {
12182     ForwardInner(to);
12183   }
12184 }
12185
12186 void
12187 RevertEvent()
12188 {
12189     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12190         return;
12191     }
12192     if (gameMode != IcsExamining) {
12193         DisplayError(_("You are not examining a game"), 0);
12194         return;
12195     }
12196     if (pausing) {
12197         DisplayError(_("You can't revert while pausing"), 0);
12198         return;
12199     }
12200     SendToICS(ics_prefix);
12201     SendToICS("revert\n");
12202 }
12203
12204 void
12205 RetractMoveEvent()
12206 {
12207     switch (gameMode) {
12208       case MachinePlaysWhite:
12209       case MachinePlaysBlack:
12210         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12211             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12212             return;
12213         }
12214         if (forwardMostMove < 2) return;
12215         currentMove = forwardMostMove = forwardMostMove - 2;
12216         whiteTimeRemaining = timeRemaining[0][currentMove];
12217         blackTimeRemaining = timeRemaining[1][currentMove];
12218         DisplayBothClocks();
12219         DisplayMove(currentMove - 1);
12220         ClearHighlights();/*!! could figure this out*/
12221         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12222         SendToProgram("remove\n", &first);
12223         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12224         break;
12225
12226       case BeginningOfGame:
12227       default:
12228         break;
12229
12230       case IcsPlayingWhite:
12231       case IcsPlayingBlack:
12232         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12233             SendToICS(ics_prefix);
12234             SendToICS("takeback 2\n");
12235         } else {
12236             SendToICS(ics_prefix);
12237             SendToICS("takeback 1\n");
12238         }
12239         break;
12240     }
12241 }
12242
12243 void
12244 MoveNowEvent()
12245 {
12246     ChessProgramState *cps;
12247
12248     switch (gameMode) {
12249       case MachinePlaysWhite:
12250         if (!WhiteOnMove(forwardMostMove)) {
12251             DisplayError(_("It is your turn"), 0);
12252             return;
12253         }
12254         cps = &first;
12255         break;
12256       case MachinePlaysBlack:
12257         if (WhiteOnMove(forwardMostMove)) {
12258             DisplayError(_("It is your turn"), 0);
12259             return;
12260         }
12261         cps = &first;
12262         break;
12263       case TwoMachinesPlay:
12264         if (WhiteOnMove(forwardMostMove) ==
12265             (first.twoMachinesColor[0] == 'w')) {
12266             cps = &first;
12267         } else {
12268             cps = &second;
12269         }
12270         break;
12271       case BeginningOfGame:
12272       default:
12273         return;
12274     }
12275     SendToProgram("?\n", cps);
12276 }
12277
12278 void
12279 TruncateGameEvent()
12280 {
12281     EditGameEvent();
12282     if (gameMode != EditGame) return;
12283     TruncateGame();
12284 }
12285
12286 void
12287 TruncateGame()
12288 {
12289     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12290     if (forwardMostMove > currentMove) {
12291         if (gameInfo.resultDetails != NULL) {
12292             free(gameInfo.resultDetails);
12293             gameInfo.resultDetails = NULL;
12294             gameInfo.result = GameUnfinished;
12295         }
12296         forwardMostMove = currentMove;
12297         HistorySet(parseList, backwardMostMove, forwardMostMove,
12298                    currentMove-1);
12299     }
12300 }
12301
12302 void
12303 HintEvent()
12304 {
12305     if (appData.noChessProgram) return;
12306     switch (gameMode) {
12307       case MachinePlaysWhite:
12308         if (WhiteOnMove(forwardMostMove)) {
12309             DisplayError(_("Wait until your turn"), 0);
12310             return;
12311         }
12312         break;
12313       case BeginningOfGame:
12314       case MachinePlaysBlack:
12315         if (!WhiteOnMove(forwardMostMove)) {
12316             DisplayError(_("Wait until your turn"), 0);
12317             return;
12318         }
12319         break;
12320       default:
12321         DisplayError(_("No hint available"), 0);
12322         return;
12323     }
12324     SendToProgram("hint\n", &first);
12325     hintRequested = TRUE;
12326 }
12327
12328 void
12329 BookEvent()
12330 {
12331     if (appData.noChessProgram) return;
12332     switch (gameMode) {
12333       case MachinePlaysWhite:
12334         if (WhiteOnMove(forwardMostMove)) {
12335             DisplayError(_("Wait until your turn"), 0);
12336             return;
12337         }
12338         break;
12339       case BeginningOfGame:
12340       case MachinePlaysBlack:
12341         if (!WhiteOnMove(forwardMostMove)) {
12342             DisplayError(_("Wait until your turn"), 0);
12343             return;
12344         }
12345         break;
12346       case EditPosition:
12347         EditPositionDone(TRUE);
12348         break;
12349       case TwoMachinesPlay:
12350         return;
12351       default:
12352         break;
12353     }
12354     SendToProgram("bk\n", &first);
12355     bookOutput[0] = NULLCHAR;
12356     bookRequested = TRUE;
12357 }
12358
12359 void
12360 AboutGameEvent()
12361 {
12362     char *tags = PGNTags(&gameInfo);
12363     TagsPopUp(tags, CmailMsg());
12364     free(tags);
12365 }
12366
12367 /* end button procedures */
12368
12369 void
12370 PrintPosition(fp, move)
12371      FILE *fp;
12372      int move;
12373 {
12374     int i, j;
12375     
12376     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12377         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12378             char c = PieceToChar(boards[move][i][j]);
12379             fputc(c == 'x' ? '.' : c, fp);
12380             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12381         }
12382     }
12383     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12384       fprintf(fp, "white to play\n");
12385     else
12386       fprintf(fp, "black to play\n");
12387 }
12388
12389 void
12390 PrintOpponents(fp)
12391      FILE *fp;
12392 {
12393     if (gameInfo.white != NULL) {
12394         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12395     } else {
12396         fprintf(fp, "\n");
12397     }
12398 }
12399
12400 /* Find last component of program's own name, using some heuristics */
12401 void
12402 TidyProgramName(prog, host, buf)
12403      char *prog, *host, buf[MSG_SIZ];
12404 {
12405     char *p, *q;
12406     int local = (strcmp(host, "localhost") == 0);
12407     while (!local && (p = strchr(prog, ';')) != NULL) {
12408         p++;
12409         while (*p == ' ') p++;
12410         prog = p;
12411     }
12412     if (*prog == '"' || *prog == '\'') {
12413         q = strchr(prog + 1, *prog);
12414     } else {
12415         q = strchr(prog, ' ');
12416     }
12417     if (q == NULL) q = prog + strlen(prog);
12418     p = q;
12419     while (p >= prog && *p != '/' && *p != '\\') p--;
12420     p++;
12421     if(p == prog && *p == '"') p++;
12422     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12423     memcpy(buf, p, q - p);
12424     buf[q - p] = NULLCHAR;
12425     if (!local) {
12426         strcat(buf, "@");
12427         strcat(buf, host);
12428     }
12429 }
12430
12431 char *
12432 TimeControlTagValue()
12433 {
12434     char buf[MSG_SIZ];
12435     if (!appData.clockMode) {
12436         strcpy(buf, "-");
12437     } else if (movesPerSession > 0) {
12438         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12439     } else if (timeIncrement == 0) {
12440         sprintf(buf, "%ld", timeControl/1000);
12441     } else {
12442         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12443     }
12444     return StrSave(buf);
12445 }
12446
12447 void
12448 SetGameInfo()
12449 {
12450     /* This routine is used only for certain modes */
12451     VariantClass v = gameInfo.variant;
12452     ChessMove r = GameUnfinished;
12453     char *p = NULL;
12454
12455     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12456         r = gameInfo.result; 
12457         p = gameInfo.resultDetails; 
12458         gameInfo.resultDetails = NULL;
12459     }
12460     ClearGameInfo(&gameInfo);
12461     gameInfo.variant = v;
12462
12463     switch (gameMode) {
12464       case MachinePlaysWhite:
12465         gameInfo.event = StrSave( appData.pgnEventHeader );
12466         gameInfo.site = StrSave(HostName());
12467         gameInfo.date = PGNDate();
12468         gameInfo.round = StrSave("-");
12469         gameInfo.white = StrSave(first.tidy);
12470         gameInfo.black = StrSave(UserName());
12471         gameInfo.timeControl = TimeControlTagValue();
12472         break;
12473
12474       case MachinePlaysBlack:
12475         gameInfo.event = StrSave( appData.pgnEventHeader );
12476         gameInfo.site = StrSave(HostName());
12477         gameInfo.date = PGNDate();
12478         gameInfo.round = StrSave("-");
12479         gameInfo.white = StrSave(UserName());
12480         gameInfo.black = StrSave(first.tidy);
12481         gameInfo.timeControl = TimeControlTagValue();
12482         break;
12483
12484       case TwoMachinesPlay:
12485         gameInfo.event = StrSave( appData.pgnEventHeader );
12486         gameInfo.site = StrSave(HostName());
12487         gameInfo.date = PGNDate();
12488         if (matchGame > 0) {
12489             char buf[MSG_SIZ];
12490             sprintf(buf, "%d", matchGame);
12491             gameInfo.round = StrSave(buf);
12492         } else {
12493             gameInfo.round = StrSave("-");
12494         }
12495         if (first.twoMachinesColor[0] == 'w') {
12496             gameInfo.white = StrSave(first.tidy);
12497             gameInfo.black = StrSave(second.tidy);
12498         } else {
12499             gameInfo.white = StrSave(second.tidy);
12500             gameInfo.black = StrSave(first.tidy);
12501         }
12502         gameInfo.timeControl = TimeControlTagValue();
12503         break;
12504
12505       case EditGame:
12506         gameInfo.event = StrSave("Edited game");
12507         gameInfo.site = StrSave(HostName());
12508         gameInfo.date = PGNDate();
12509         gameInfo.round = StrSave("-");
12510         gameInfo.white = StrSave("-");
12511         gameInfo.black = StrSave("-");
12512         gameInfo.result = r;
12513         gameInfo.resultDetails = p;
12514         break;
12515
12516       case EditPosition:
12517         gameInfo.event = StrSave("Edited position");
12518         gameInfo.site = StrSave(HostName());
12519         gameInfo.date = PGNDate();
12520         gameInfo.round = StrSave("-");
12521         gameInfo.white = StrSave("-");
12522         gameInfo.black = StrSave("-");
12523         break;
12524
12525       case IcsPlayingWhite:
12526       case IcsPlayingBlack:
12527       case IcsObserving:
12528       case IcsExamining:
12529         break;
12530
12531       case PlayFromGameFile:
12532         gameInfo.event = StrSave("Game from non-PGN file");
12533         gameInfo.site = StrSave(HostName());
12534         gameInfo.date = PGNDate();
12535         gameInfo.round = StrSave("-");
12536         gameInfo.white = StrSave("?");
12537         gameInfo.black = StrSave("?");
12538         break;
12539
12540       default:
12541         break;
12542     }
12543 }
12544
12545 void
12546 ReplaceComment(index, text)
12547      int index;
12548      char *text;
12549 {
12550     int len;
12551
12552     while (*text == '\n') text++;
12553     len = strlen(text);
12554     while (len > 0 && text[len - 1] == '\n') len--;
12555
12556     if (commentList[index] != NULL)
12557       free(commentList[index]);
12558
12559     if (len == 0) {
12560         commentList[index] = NULL;
12561         return;
12562     }
12563   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12564       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12565       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12566     commentList[index] = (char *) malloc(len + 2);
12567     strncpy(commentList[index], text, len);
12568     commentList[index][len] = '\n';
12569     commentList[index][len + 1] = NULLCHAR;
12570   } else { 
12571     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12572     char *p;
12573     commentList[index] = (char *) malloc(len + 6);
12574     strcpy(commentList[index], "{\n");
12575     strncpy(commentList[index]+2, text, len);
12576     commentList[index][len+2] = NULLCHAR;
12577     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12578     strcat(commentList[index], "\n}\n");
12579   }
12580 }
12581
12582 void
12583 CrushCRs(text)
12584      char *text;
12585 {
12586   char *p = text;
12587   char *q = text;
12588   char ch;
12589
12590   do {
12591     ch = *p++;
12592     if (ch == '\r') continue;
12593     *q++ = ch;
12594   } while (ch != '\0');
12595 }
12596
12597 void
12598 AppendComment(index, text, addBraces)
12599      int index;
12600      char *text;
12601      Boolean addBraces; // [HGM] braces: tells if we should add {}
12602 {
12603     int oldlen, len;
12604     char *old;
12605
12606 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12607     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12608
12609     CrushCRs(text);
12610     while (*text == '\n') text++;
12611     len = strlen(text);
12612     while (len > 0 && text[len - 1] == '\n') len--;
12613
12614     if (len == 0) return;
12615
12616     if (commentList[index] != NULL) {
12617         old = commentList[index];
12618         oldlen = strlen(old);
12619         while(commentList[index][oldlen-1] ==  '\n')
12620           commentList[index][--oldlen] = NULLCHAR;
12621         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12622         strcpy(commentList[index], old);
12623         free(old);
12624         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12625         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12626           if(addBraces) addBraces = FALSE; else { text++; len--; }
12627           while (*text == '\n') { text++; len--; }
12628           commentList[index][--oldlen] = NULLCHAR;
12629       }
12630         if(addBraces) strcat(commentList[index], "\n{\n");
12631         else          strcat(commentList[index], "\n");
12632         strcat(commentList[index], text);
12633         if(addBraces) strcat(commentList[index], "\n}\n");
12634         else          strcat(commentList[index], "\n");
12635     } else {
12636         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12637         if(addBraces)
12638              strcpy(commentList[index], "{\n");
12639         else commentList[index][0] = NULLCHAR;
12640         strcat(commentList[index], text);
12641         strcat(commentList[index], "\n");
12642         if(addBraces) strcat(commentList[index], "}\n");
12643     }
12644 }
12645
12646 static char * FindStr( char * text, char * sub_text )
12647 {
12648     char * result = strstr( text, sub_text );
12649
12650     if( result != NULL ) {
12651         result += strlen( sub_text );
12652     }
12653
12654     return result;
12655 }
12656
12657 /* [AS] Try to extract PV info from PGN comment */
12658 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12659 char *GetInfoFromComment( int index, char * text )
12660 {
12661     char * sep = text;
12662
12663     if( text != NULL && index > 0 ) {
12664         int score = 0;
12665         int depth = 0;
12666         int time = -1, sec = 0, deci;
12667         char * s_eval = FindStr( text, "[%eval " );
12668         char * s_emt = FindStr( text, "[%emt " );
12669
12670         if( s_eval != NULL || s_emt != NULL ) {
12671             /* New style */
12672             char delim;
12673
12674             if( s_eval != NULL ) {
12675                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12676                     return text;
12677                 }
12678
12679                 if( delim != ']' ) {
12680                     return text;
12681                 }
12682             }
12683
12684             if( s_emt != NULL ) {
12685             }
12686                 return text;
12687         }
12688         else {
12689             /* We expect something like: [+|-]nnn.nn/dd */
12690             int score_lo = 0;
12691
12692             if(*text != '{') return text; // [HGM] braces: must be normal comment
12693
12694             sep = strchr( text, '/' );
12695             if( sep == NULL || sep < (text+4) ) {
12696                 return text;
12697             }
12698
12699             time = -1; sec = -1; deci = -1;
12700             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12701                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12702                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12703                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12704                 return text;
12705             }
12706
12707             if( score_lo < 0 || score_lo >= 100 ) {
12708                 return text;
12709             }
12710
12711             if(sec >= 0) time = 600*time + 10*sec; else
12712             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12713
12714             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12715
12716             /* [HGM] PV time: now locate end of PV info */
12717             while( *++sep >= '0' && *sep <= '9'); // strip depth
12718             if(time >= 0)
12719             while( *++sep >= '0' && *sep <= '9'); // strip time
12720             if(sec >= 0)
12721             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12722             if(deci >= 0)
12723             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12724             while(*sep == ' ') sep++;
12725         }
12726
12727         if( depth <= 0 ) {
12728             return text;
12729         }
12730
12731         if( time < 0 ) {
12732             time = -1;
12733         }
12734
12735         pvInfoList[index-1].depth = depth;
12736         pvInfoList[index-1].score = score;
12737         pvInfoList[index-1].time  = 10*time; // centi-sec
12738         if(*sep == '}') *sep = 0; else *--sep = '{';
12739     }
12740     return sep;
12741 }
12742
12743 void
12744 SendToProgram(message, cps)
12745      char *message;
12746      ChessProgramState *cps;
12747 {
12748     int count, outCount, error;
12749     char buf[MSG_SIZ];
12750
12751     if (cps->pr == NULL) return;
12752     Attention(cps);
12753     
12754     if (appData.debugMode) {
12755         TimeMark now;
12756         GetTimeMark(&now);
12757         fprintf(debugFP, "%ld >%-6s: %s", 
12758                 SubtractTimeMarks(&now, &programStartTime),
12759                 cps->which, message);
12760     }
12761     
12762     count = strlen(message);
12763     outCount = OutputToProcess(cps->pr, message, count, &error);
12764     if (outCount < count && !exiting 
12765                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12766         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12767         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12768             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12769                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12770                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12771             } else {
12772                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12773             }
12774             gameInfo.resultDetails = StrSave(buf);
12775         }
12776         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12777     }
12778 }
12779
12780 void
12781 ReceiveFromProgram(isr, closure, message, count, error)
12782      InputSourceRef isr;
12783      VOIDSTAR closure;
12784      char *message;
12785      int count;
12786      int error;
12787 {
12788     char *end_str;
12789     char buf[MSG_SIZ];
12790     ChessProgramState *cps = (ChessProgramState *)closure;
12791
12792     if (isr != cps->isr) return; /* Killed intentionally */
12793     if (count <= 0) {
12794         if (count == 0) {
12795             sprintf(buf,
12796                     _("Error: %s chess program (%s) exited unexpectedly"),
12797                     cps->which, cps->program);
12798         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12799                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12800                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12801                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12802                 } else {
12803                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12804                 }
12805                 gameInfo.resultDetails = StrSave(buf);
12806             }
12807             RemoveInputSource(cps->isr);
12808             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12809         } else {
12810             sprintf(buf,
12811                     _("Error reading from %s chess program (%s)"),
12812                     cps->which, cps->program);
12813             RemoveInputSource(cps->isr);
12814
12815             /* [AS] Program is misbehaving badly... kill it */
12816             if( count == -2 ) {
12817                 DestroyChildProcess( cps->pr, 9 );
12818                 cps->pr = NoProc;
12819             }
12820
12821             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12822         }
12823         return;
12824     }
12825     
12826     if ((end_str = strchr(message, '\r')) != NULL)
12827       *end_str = NULLCHAR;
12828     if ((end_str = strchr(message, '\n')) != NULL)
12829       *end_str = NULLCHAR;
12830     
12831     if (appData.debugMode) {
12832         TimeMark now; int print = 1;
12833         char *quote = ""; char c; int i;
12834
12835         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12836                 char start = message[0];
12837                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12838                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12839                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12840                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12841                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12842                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12843                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12844                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12845                         { quote = "# "; print = (appData.engineComments == 2); }
12846                 message[0] = start; // restore original message
12847         }
12848         if(print) {
12849                 GetTimeMark(&now);
12850                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12851                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12852                         quote,
12853                         message);
12854         }
12855     }
12856
12857     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12858     if (appData.icsEngineAnalyze) {
12859         if (strstr(message, "whisper") != NULL ||
12860              strstr(message, "kibitz") != NULL || 
12861             strstr(message, "tellics") != NULL) return;
12862     }
12863
12864     HandleMachineMove(message, cps);
12865 }
12866
12867
12868 void
12869 SendTimeControl(cps, mps, tc, inc, sd, st)
12870      ChessProgramState *cps;
12871      int mps, inc, sd, st;
12872      long tc;
12873 {
12874     char buf[MSG_SIZ];
12875     int seconds;
12876
12877     if( timeControl_2 > 0 ) {
12878         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12879             tc = timeControl_2;
12880         }
12881     }
12882     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12883     inc /= cps->timeOdds;
12884     st  /= cps->timeOdds;
12885
12886     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12887
12888     if (st > 0) {
12889       /* Set exact time per move, normally using st command */
12890       if (cps->stKludge) {
12891         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12892         seconds = st % 60;
12893         if (seconds == 0) {
12894           sprintf(buf, "level 1 %d\n", st/60);
12895         } else {
12896           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12897         }
12898       } else {
12899         sprintf(buf, "st %d\n", st);
12900       }
12901     } else {
12902       /* Set conventional or incremental time control, using level command */
12903       if (seconds == 0) {
12904         /* Note old gnuchess bug -- minutes:seconds used to not work.
12905            Fixed in later versions, but still avoid :seconds
12906            when seconds is 0. */
12907         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12908       } else {
12909         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12910                 seconds, inc/1000);
12911       }
12912     }
12913     SendToProgram(buf, cps);
12914
12915     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12916     /* Orthogonally, limit search to given depth */
12917     if (sd > 0) {
12918       if (cps->sdKludge) {
12919         sprintf(buf, "depth\n%d\n", sd);
12920       } else {
12921         sprintf(buf, "sd %d\n", sd);
12922       }
12923       SendToProgram(buf, cps);
12924     }
12925
12926     if(cps->nps > 0) { /* [HGM] nps */
12927         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12928         else {
12929                 sprintf(buf, "nps %d\n", cps->nps);
12930               SendToProgram(buf, cps);
12931         }
12932     }
12933 }
12934
12935 ChessProgramState *WhitePlayer()
12936 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12937 {
12938     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12939        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12940         return &second;
12941     return &first;
12942 }
12943
12944 void
12945 SendTimeRemaining(cps, machineWhite)
12946      ChessProgramState *cps;
12947      int /*boolean*/ machineWhite;
12948 {
12949     char message[MSG_SIZ];
12950     long time, otime;
12951
12952     /* Note: this routine must be called when the clocks are stopped
12953        or when they have *just* been set or switched; otherwise
12954        it will be off by the time since the current tick started.
12955     */
12956     if (machineWhite) {
12957         time = whiteTimeRemaining / 10;
12958         otime = blackTimeRemaining / 10;
12959     } else {
12960         time = blackTimeRemaining / 10;
12961         otime = whiteTimeRemaining / 10;
12962     }
12963     /* [HGM] translate opponent's time by time-odds factor */
12964     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12965     if (appData.debugMode) {
12966         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12967     }
12968
12969     if (time <= 0) time = 1;
12970     if (otime <= 0) otime = 1;
12971     
12972     sprintf(message, "time %ld\n", time);
12973     SendToProgram(message, cps);
12974
12975     sprintf(message, "otim %ld\n", otime);
12976     SendToProgram(message, cps);
12977 }
12978
12979 int
12980 BoolFeature(p, name, loc, cps)
12981      char **p;
12982      char *name;
12983      int *loc;
12984      ChessProgramState *cps;
12985 {
12986   char buf[MSG_SIZ];
12987   int len = strlen(name);
12988   int val;
12989   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12990     (*p) += len + 1;
12991     sscanf(*p, "%d", &val);
12992     *loc = (val != 0);
12993     while (**p && **p != ' ') (*p)++;
12994     sprintf(buf, "accepted %s\n", name);
12995     SendToProgram(buf, cps);
12996     return TRUE;
12997   }
12998   return FALSE;
12999 }
13000
13001 int
13002 IntFeature(p, name, loc, cps)
13003      char **p;
13004      char *name;
13005      int *loc;
13006      ChessProgramState *cps;
13007 {
13008   char buf[MSG_SIZ];
13009   int len = strlen(name);
13010   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13011     (*p) += len + 1;
13012     sscanf(*p, "%d", loc);
13013     while (**p && **p != ' ') (*p)++;
13014     sprintf(buf, "accepted %s\n", name);
13015     SendToProgram(buf, cps);
13016     return TRUE;
13017   }
13018   return FALSE;
13019 }
13020
13021 int
13022 StringFeature(p, name, loc, cps)
13023      char **p;
13024      char *name;
13025      char loc[];
13026      ChessProgramState *cps;
13027 {
13028   char buf[MSG_SIZ];
13029   int len = strlen(name);
13030   if (strncmp((*p), name, len) == 0
13031       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13032     (*p) += len + 2;
13033     sscanf(*p, "%[^\"]", loc);
13034     while (**p && **p != '\"') (*p)++;
13035     if (**p == '\"') (*p)++;
13036     sprintf(buf, "accepted %s\n", name);
13037     SendToProgram(buf, cps);
13038     return TRUE;
13039   }
13040   return FALSE;
13041 }
13042
13043 int 
13044 ParseOption(Option *opt, ChessProgramState *cps)
13045 // [HGM] options: process the string that defines an engine option, and determine
13046 // name, type, default value, and allowed value range
13047 {
13048         char *p, *q, buf[MSG_SIZ];
13049         int n, min = (-1)<<31, max = 1<<31, def;
13050
13051         if(p = strstr(opt->name, " -spin ")) {
13052             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13053             if(max < min) max = min; // enforce consistency
13054             if(def < min) def = min;
13055             if(def > max) def = max;
13056             opt->value = def;
13057             opt->min = min;
13058             opt->max = max;
13059             opt->type = Spin;
13060         } else if((p = strstr(opt->name, " -slider "))) {
13061             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13062             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13063             if(max < min) max = min; // enforce consistency
13064             if(def < min) def = min;
13065             if(def > max) def = max;
13066             opt->value = def;
13067             opt->min = min;
13068             opt->max = max;
13069             opt->type = Spin; // Slider;
13070         } else if((p = strstr(opt->name, " -string "))) {
13071             opt->textValue = p+9;
13072             opt->type = TextBox;
13073         } else if((p = strstr(opt->name, " -file "))) {
13074             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13075             opt->textValue = p+7;
13076             opt->type = TextBox; // FileName;
13077         } else if((p = strstr(opt->name, " -path "))) {
13078             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13079             opt->textValue = p+7;
13080             opt->type = TextBox; // PathName;
13081         } else if(p = strstr(opt->name, " -check ")) {
13082             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13083             opt->value = (def != 0);
13084             opt->type = CheckBox;
13085         } else if(p = strstr(opt->name, " -combo ")) {
13086             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13087             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13088             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13089             opt->value = n = 0;
13090             while(q = StrStr(q, " /// ")) {
13091                 n++; *q = 0;    // count choices, and null-terminate each of them
13092                 q += 5;
13093                 if(*q == '*') { // remember default, which is marked with * prefix
13094                     q++;
13095                     opt->value = n;
13096                 }
13097                 cps->comboList[cps->comboCnt++] = q;
13098             }
13099             cps->comboList[cps->comboCnt++] = NULL;
13100             opt->max = n + 1;
13101             opt->type = ComboBox;
13102         } else if(p = strstr(opt->name, " -button")) {
13103             opt->type = Button;
13104         } else if(p = strstr(opt->name, " -save")) {
13105             opt->type = SaveButton;
13106         } else return FALSE;
13107         *p = 0; // terminate option name
13108         // now look if the command-line options define a setting for this engine option.
13109         if(cps->optionSettings && cps->optionSettings[0])
13110             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13111         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13112                 sprintf(buf, "option %s", p);
13113                 if(p = strstr(buf, ",")) *p = 0;
13114                 strcat(buf, "\n");
13115                 SendToProgram(buf, cps);
13116         }
13117         return TRUE;
13118 }
13119
13120 void
13121 FeatureDone(cps, val)
13122      ChessProgramState* cps;
13123      int val;
13124 {
13125   DelayedEventCallback cb = GetDelayedEvent();
13126   if ((cb == InitBackEnd3 && cps == &first) ||
13127       (cb == TwoMachinesEventIfReady && cps == &second)) {
13128     CancelDelayedEvent();
13129     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13130   }
13131   cps->initDone = val;
13132 }
13133
13134 /* Parse feature command from engine */
13135 void
13136 ParseFeatures(args, cps)
13137      char* args;
13138      ChessProgramState *cps;  
13139 {
13140   char *p = args;
13141   char *q;
13142   int val;
13143   char buf[MSG_SIZ];
13144
13145   for (;;) {
13146     while (*p == ' ') p++;
13147     if (*p == NULLCHAR) return;
13148
13149     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13150     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13151     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13152     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13153     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13154     if (BoolFeature(&p, "reuse", &val, cps)) {
13155       /* Engine can disable reuse, but can't enable it if user said no */
13156       if (!val) cps->reuse = FALSE;
13157       continue;
13158     }
13159     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13160     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13161       if (gameMode == TwoMachinesPlay) {
13162         DisplayTwoMachinesTitle();
13163       } else {
13164         DisplayTitle("");
13165       }
13166       continue;
13167     }
13168     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13169     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13170     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13171     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13172     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13173     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13174     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13175     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13176     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13177     if (IntFeature(&p, "done", &val, cps)) {
13178       FeatureDone(cps, val);
13179       continue;
13180     }
13181     /* Added by Tord: */
13182     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13183     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13184     /* End of additions by Tord */
13185
13186     /* [HGM] added features: */
13187     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13188     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13189     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13190     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13191     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13192     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13193     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13194         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13195             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13196             SendToProgram(buf, cps);
13197             continue;
13198         }
13199         if(cps->nrOptions >= MAX_OPTIONS) {
13200             cps->nrOptions--;
13201             sprintf(buf, "%s engine has too many options\n", cps->which);
13202             DisplayError(buf, 0);
13203         }
13204         continue;
13205     }
13206     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13207     /* End of additions by HGM */
13208
13209     /* unknown feature: complain and skip */
13210     q = p;
13211     while (*q && *q != '=') q++;
13212     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13213     SendToProgram(buf, cps);
13214     p = q;
13215     if (*p == '=') {
13216       p++;
13217       if (*p == '\"') {
13218         p++;
13219         while (*p && *p != '\"') p++;
13220         if (*p == '\"') p++;
13221       } else {
13222         while (*p && *p != ' ') p++;
13223       }
13224     }
13225   }
13226
13227 }
13228
13229 void
13230 PeriodicUpdatesEvent(newState)
13231      int newState;
13232 {
13233     if (newState == appData.periodicUpdates)
13234       return;
13235
13236     appData.periodicUpdates=newState;
13237
13238     /* Display type changes, so update it now */
13239 //    DisplayAnalysis();
13240
13241     /* Get the ball rolling again... */
13242     if (newState) {
13243         AnalysisPeriodicEvent(1);
13244         StartAnalysisClock();
13245     }
13246 }
13247
13248 void
13249 PonderNextMoveEvent(newState)
13250      int newState;
13251 {
13252     if (newState == appData.ponderNextMove) return;
13253     if (gameMode == EditPosition) EditPositionDone(TRUE);
13254     if (newState) {
13255         SendToProgram("hard\n", &first);
13256         if (gameMode == TwoMachinesPlay) {
13257             SendToProgram("hard\n", &second);
13258         }
13259     } else {
13260         SendToProgram("easy\n", &first);
13261         thinkOutput[0] = NULLCHAR;
13262         if (gameMode == TwoMachinesPlay) {
13263             SendToProgram("easy\n", &second);
13264         }
13265     }
13266     appData.ponderNextMove = newState;
13267 }
13268
13269 void
13270 NewSettingEvent(option, command, value)
13271      char *command;
13272      int option, value;
13273 {
13274     char buf[MSG_SIZ];
13275
13276     if (gameMode == EditPosition) EditPositionDone(TRUE);
13277     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13278     SendToProgram(buf, &first);
13279     if (gameMode == TwoMachinesPlay) {
13280         SendToProgram(buf, &second);
13281     }
13282 }
13283
13284 void
13285 ShowThinkingEvent()
13286 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13287 {
13288     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13289     int newState = appData.showThinking
13290         // [HGM] thinking: other features now need thinking output as well
13291         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13292     
13293     if (oldState == newState) return;
13294     oldState = newState;
13295     if (gameMode == EditPosition) EditPositionDone(TRUE);
13296     if (oldState) {
13297         SendToProgram("post\n", &first);
13298         if (gameMode == TwoMachinesPlay) {
13299             SendToProgram("post\n", &second);
13300         }
13301     } else {
13302         SendToProgram("nopost\n", &first);
13303         thinkOutput[0] = NULLCHAR;
13304         if (gameMode == TwoMachinesPlay) {
13305             SendToProgram("nopost\n", &second);
13306         }
13307     }
13308 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13309 }
13310
13311 void
13312 AskQuestionEvent(title, question, replyPrefix, which)
13313      char *title; char *question; char *replyPrefix; char *which;
13314 {
13315   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13316   if (pr == NoProc) return;
13317   AskQuestion(title, question, replyPrefix, pr);
13318 }
13319
13320 void
13321 DisplayMove(moveNumber)
13322      int moveNumber;
13323 {
13324     char message[MSG_SIZ];
13325     char res[MSG_SIZ];
13326     char cpThinkOutput[MSG_SIZ];
13327
13328     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13329     
13330     if (moveNumber == forwardMostMove - 1 || 
13331         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13332
13333         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13334
13335         if (strchr(cpThinkOutput, '\n')) {
13336             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13337         }
13338     } else {
13339         *cpThinkOutput = NULLCHAR;
13340     }
13341
13342     /* [AS] Hide thinking from human user */
13343     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13344         *cpThinkOutput = NULLCHAR;
13345         if( thinkOutput[0] != NULLCHAR ) {
13346             int i;
13347
13348             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13349                 cpThinkOutput[i] = '.';
13350             }
13351             cpThinkOutput[i] = NULLCHAR;
13352             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13353         }
13354     }
13355
13356     if (moveNumber == forwardMostMove - 1 &&
13357         gameInfo.resultDetails != NULL) {
13358         if (gameInfo.resultDetails[0] == NULLCHAR) {
13359             sprintf(res, " %s", PGNResult(gameInfo.result));
13360         } else {
13361             sprintf(res, " {%s} %s",
13362                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13363         }
13364     } else {
13365         res[0] = NULLCHAR;
13366     }
13367
13368     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13369         DisplayMessage(res, cpThinkOutput);
13370     } else {
13371         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13372                 WhiteOnMove(moveNumber) ? " " : ".. ",
13373                 parseList[moveNumber], res);
13374         DisplayMessage(message, cpThinkOutput);
13375     }
13376 }
13377
13378 void
13379 DisplayComment(moveNumber, text)
13380      int moveNumber;
13381      char *text;
13382 {
13383     char title[MSG_SIZ];
13384     char buf[8000]; // comment can be long!
13385     int score, depth;
13386     
13387     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13388       strcpy(title, "Comment");
13389     } else {
13390       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13391               WhiteOnMove(moveNumber) ? " " : ".. ",
13392               parseList[moveNumber]);
13393     }
13394     // [HGM] PV info: display PV info together with (or as) comment
13395     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13396       if(text == NULL) text = "";                                           
13397       score = pvInfoList[moveNumber].score;
13398       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13399               depth, (pvInfoList[moveNumber].time+50)/100, text);
13400       text = buf;
13401     }
13402     if (text != NULL && (appData.autoDisplayComment || commentUp))
13403         CommentPopUp(title, text);
13404 }
13405
13406 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13407  * might be busy thinking or pondering.  It can be omitted if your
13408  * gnuchess is configured to stop thinking immediately on any user
13409  * input.  However, that gnuchess feature depends on the FIONREAD
13410  * ioctl, which does not work properly on some flavors of Unix.
13411  */
13412 void
13413 Attention(cps)
13414      ChessProgramState *cps;
13415 {
13416 #if ATTENTION
13417     if (!cps->useSigint) return;
13418     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13419     switch (gameMode) {
13420       case MachinePlaysWhite:
13421       case MachinePlaysBlack:
13422       case TwoMachinesPlay:
13423       case IcsPlayingWhite:
13424       case IcsPlayingBlack:
13425       case AnalyzeMode:
13426       case AnalyzeFile:
13427         /* Skip if we know it isn't thinking */
13428         if (!cps->maybeThinking) return;
13429         if (appData.debugMode)
13430           fprintf(debugFP, "Interrupting %s\n", cps->which);
13431         InterruptChildProcess(cps->pr);
13432         cps->maybeThinking = FALSE;
13433         break;
13434       default:
13435         break;
13436     }
13437 #endif /*ATTENTION*/
13438 }
13439
13440 int
13441 CheckFlags()
13442 {
13443     if (whiteTimeRemaining <= 0) {
13444         if (!whiteFlag) {
13445             whiteFlag = TRUE;
13446             if (appData.icsActive) {
13447                 if (appData.autoCallFlag &&
13448                     gameMode == IcsPlayingBlack && !blackFlag) {
13449                   SendToICS(ics_prefix);
13450                   SendToICS("flag\n");
13451                 }
13452             } else {
13453                 if (blackFlag) {
13454                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13455                 } else {
13456                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13457                     if (appData.autoCallFlag) {
13458                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13459                         return TRUE;
13460                     }
13461                 }
13462             }
13463         }
13464     }
13465     if (blackTimeRemaining <= 0) {
13466         if (!blackFlag) {
13467             blackFlag = TRUE;
13468             if (appData.icsActive) {
13469                 if (appData.autoCallFlag &&
13470                     gameMode == IcsPlayingWhite && !whiteFlag) {
13471                   SendToICS(ics_prefix);
13472                   SendToICS("flag\n");
13473                 }
13474             } else {
13475                 if (whiteFlag) {
13476                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13477                 } else {
13478                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13479                     if (appData.autoCallFlag) {
13480                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13481                         return TRUE;
13482                     }
13483                 }
13484             }
13485         }
13486     }
13487     return FALSE;
13488 }
13489
13490 void
13491 CheckTimeControl()
13492 {
13493     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13494         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13495
13496     /*
13497      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13498      */
13499     if ( !WhiteOnMove(forwardMostMove) )
13500         /* White made time control */
13501         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13502         /* [HGM] time odds: correct new time quota for time odds! */
13503                                             / WhitePlayer()->timeOdds;
13504       else
13505         /* Black made time control */
13506         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13507                                             / WhitePlayer()->other->timeOdds;
13508 }
13509
13510 void
13511 DisplayBothClocks()
13512 {
13513     int wom = gameMode == EditPosition ?
13514       !blackPlaysFirst : WhiteOnMove(currentMove);
13515     DisplayWhiteClock(whiteTimeRemaining, wom);
13516     DisplayBlackClock(blackTimeRemaining, !wom);
13517 }
13518
13519
13520 /* Timekeeping seems to be a portability nightmare.  I think everyone
13521    has ftime(), but I'm really not sure, so I'm including some ifdefs
13522    to use other calls if you don't.  Clocks will be less accurate if
13523    you have neither ftime nor gettimeofday.
13524 */
13525
13526 /* VS 2008 requires the #include outside of the function */
13527 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13528 #include <sys/timeb.h>
13529 #endif
13530
13531 /* Get the current time as a TimeMark */
13532 void
13533 GetTimeMark(tm)
13534      TimeMark *tm;
13535 {
13536 #if HAVE_GETTIMEOFDAY
13537
13538     struct timeval timeVal;
13539     struct timezone timeZone;
13540
13541     gettimeofday(&timeVal, &timeZone);
13542     tm->sec = (long) timeVal.tv_sec; 
13543     tm->ms = (int) (timeVal.tv_usec / 1000L);
13544
13545 #else /*!HAVE_GETTIMEOFDAY*/
13546 #if HAVE_FTIME
13547
13548 // include <sys/timeb.h> / moved to just above start of function
13549     struct timeb timeB;
13550
13551     ftime(&timeB);
13552     tm->sec = (long) timeB.time;
13553     tm->ms = (int) timeB.millitm;
13554
13555 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13556     tm->sec = (long) time(NULL);
13557     tm->ms = 0;
13558 #endif
13559 #endif
13560 }
13561
13562 /* Return the difference in milliseconds between two
13563    time marks.  We assume the difference will fit in a long!
13564 */
13565 long
13566 SubtractTimeMarks(tm2, tm1)
13567      TimeMark *tm2, *tm1;
13568 {
13569     return 1000L*(tm2->sec - tm1->sec) +
13570            (long) (tm2->ms - tm1->ms);
13571 }
13572
13573
13574 /*
13575  * Code to manage the game clocks.
13576  *
13577  * In tournament play, black starts the clock and then white makes a move.
13578  * We give the human user a slight advantage if he is playing white---the
13579  * clocks don't run until he makes his first move, so it takes zero time.
13580  * Also, we don't account for network lag, so we could get out of sync
13581  * with GNU Chess's clock -- but then, referees are always right.  
13582  */
13583
13584 static TimeMark tickStartTM;
13585 static long intendedTickLength;
13586
13587 long
13588 NextTickLength(timeRemaining)
13589      long timeRemaining;
13590 {
13591     long nominalTickLength, nextTickLength;
13592
13593     if (timeRemaining > 0L && timeRemaining <= 10000L)
13594       nominalTickLength = 100L;
13595     else
13596       nominalTickLength = 1000L;
13597     nextTickLength = timeRemaining % nominalTickLength;
13598     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13599
13600     return nextTickLength;
13601 }
13602
13603 /* Adjust clock one minute up or down */
13604 void
13605 AdjustClock(Boolean which, int dir)
13606 {
13607     if(which) blackTimeRemaining += 60000*dir;
13608     else      whiteTimeRemaining += 60000*dir;
13609     DisplayBothClocks();
13610 }
13611
13612 /* Stop clocks and reset to a fresh time control */
13613 void
13614 ResetClocks() 
13615 {
13616     (void) StopClockTimer();
13617     if (appData.icsActive) {
13618         whiteTimeRemaining = blackTimeRemaining = 0;
13619     } else if (searchTime) {
13620         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13621         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13622     } else { /* [HGM] correct new time quote for time odds */
13623         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13624         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13625     }
13626     if (whiteFlag || blackFlag) {
13627         DisplayTitle("");
13628         whiteFlag = blackFlag = FALSE;
13629     }
13630     DisplayBothClocks();
13631 }
13632
13633 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13634
13635 /* Decrement running clock by amount of time that has passed */
13636 void
13637 DecrementClocks()
13638 {
13639     long timeRemaining;
13640     long lastTickLength, fudge;
13641     TimeMark now;
13642
13643     if (!appData.clockMode) return;
13644     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13645         
13646     GetTimeMark(&now);
13647
13648     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13649
13650     /* Fudge if we woke up a little too soon */
13651     fudge = intendedTickLength - lastTickLength;
13652     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13653
13654     if (WhiteOnMove(forwardMostMove)) {
13655         if(whiteNPS >= 0) lastTickLength = 0;
13656         timeRemaining = whiteTimeRemaining -= lastTickLength;
13657         DisplayWhiteClock(whiteTimeRemaining - fudge,
13658                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13659     } else {
13660         if(blackNPS >= 0) lastTickLength = 0;
13661         timeRemaining = blackTimeRemaining -= lastTickLength;
13662         DisplayBlackClock(blackTimeRemaining - fudge,
13663                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13664     }
13665
13666     if (CheckFlags()) return;
13667         
13668     tickStartTM = now;
13669     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13670     StartClockTimer(intendedTickLength);
13671
13672     /* if the time remaining has fallen below the alarm threshold, sound the
13673      * alarm. if the alarm has sounded and (due to a takeback or time control
13674      * with increment) the time remaining has increased to a level above the
13675      * threshold, reset the alarm so it can sound again. 
13676      */
13677     
13678     if (appData.icsActive && appData.icsAlarm) {
13679
13680         /* make sure we are dealing with the user's clock */
13681         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13682                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13683            )) return;
13684
13685         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13686             alarmSounded = FALSE;
13687         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13688             PlayAlarmSound();
13689             alarmSounded = TRUE;
13690         }
13691     }
13692 }
13693
13694
13695 /* A player has just moved, so stop the previously running
13696    clock and (if in clock mode) start the other one.
13697    We redisplay both clocks in case we're in ICS mode, because
13698    ICS gives us an update to both clocks after every move.
13699    Note that this routine is called *after* forwardMostMove
13700    is updated, so the last fractional tick must be subtracted
13701    from the color that is *not* on move now.
13702 */
13703 void
13704 SwitchClocks()
13705 {
13706     long lastTickLength;
13707     TimeMark now;
13708     int flagged = FALSE;
13709
13710     GetTimeMark(&now);
13711
13712     if (StopClockTimer() && appData.clockMode) {
13713         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13714         if (WhiteOnMove(forwardMostMove)) {
13715             if(blackNPS >= 0) lastTickLength = 0;
13716             blackTimeRemaining -= lastTickLength;
13717            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13718 //         if(pvInfoList[forwardMostMove-1].time == -1)
13719                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13720                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13721         } else {
13722            if(whiteNPS >= 0) lastTickLength = 0;
13723            whiteTimeRemaining -= lastTickLength;
13724            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13725 //         if(pvInfoList[forwardMostMove-1].time == -1)
13726                  pvInfoList[forwardMostMove-1].time = 
13727                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13728         }
13729         flagged = CheckFlags();
13730     }
13731     CheckTimeControl();
13732
13733     if (flagged || !appData.clockMode) return;
13734
13735     switch (gameMode) {
13736       case MachinePlaysBlack:
13737       case MachinePlaysWhite:
13738       case BeginningOfGame:
13739         if (pausing) return;
13740         break;
13741
13742       case EditGame:
13743       case PlayFromGameFile:
13744       case IcsExamining:
13745         return;
13746
13747       default:
13748         break;
13749     }
13750
13751     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13752         if(WhiteOnMove(forwardMostMove))
13753              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13754         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13755     }
13756
13757     tickStartTM = now;
13758     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13759       whiteTimeRemaining : blackTimeRemaining);
13760     StartClockTimer(intendedTickLength);
13761 }
13762         
13763
13764 /* Stop both clocks */
13765 void
13766 StopClocks()
13767 {       
13768     long lastTickLength;
13769     TimeMark now;
13770
13771     if (!StopClockTimer()) return;
13772     if (!appData.clockMode) return;
13773
13774     GetTimeMark(&now);
13775
13776     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13777     if (WhiteOnMove(forwardMostMove)) {
13778         if(whiteNPS >= 0) lastTickLength = 0;
13779         whiteTimeRemaining -= lastTickLength;
13780         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13781     } else {
13782         if(blackNPS >= 0) lastTickLength = 0;
13783         blackTimeRemaining -= lastTickLength;
13784         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13785     }
13786     CheckFlags();
13787 }
13788         
13789 /* Start clock of player on move.  Time may have been reset, so
13790    if clock is already running, stop and restart it. */
13791 void
13792 StartClocks()
13793 {
13794     (void) StopClockTimer(); /* in case it was running already */
13795     DisplayBothClocks();
13796     if (CheckFlags()) return;
13797
13798     if (!appData.clockMode) return;
13799     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13800
13801     GetTimeMark(&tickStartTM);
13802     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13803       whiteTimeRemaining : blackTimeRemaining);
13804
13805    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13806     whiteNPS = blackNPS = -1; 
13807     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13808        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13809         whiteNPS = first.nps;
13810     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13811        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13812         blackNPS = first.nps;
13813     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13814         whiteNPS = second.nps;
13815     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13816         blackNPS = second.nps;
13817     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13818
13819     StartClockTimer(intendedTickLength);
13820 }
13821
13822 char *
13823 TimeString(ms)
13824      long ms;
13825 {
13826     long second, minute, hour, day;
13827     char *sign = "";
13828     static char buf[32];
13829     
13830     if (ms > 0 && ms <= 9900) {
13831       /* convert milliseconds to tenths, rounding up */
13832       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13833
13834       sprintf(buf, " %03.1f ", tenths/10.0);
13835       return buf;
13836     }
13837
13838     /* convert milliseconds to seconds, rounding up */
13839     /* use floating point to avoid strangeness of integer division
13840        with negative dividends on many machines */
13841     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13842
13843     if (second < 0) {
13844         sign = "-";
13845         second = -second;
13846     }
13847     
13848     day = second / (60 * 60 * 24);
13849     second = second % (60 * 60 * 24);
13850     hour = second / (60 * 60);
13851     second = second % (60 * 60);
13852     minute = second / 60;
13853     second = second % 60;
13854     
13855     if (day > 0)
13856       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13857               sign, day, hour, minute, second);
13858     else if (hour > 0)
13859       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13860     else
13861       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13862     
13863     return buf;
13864 }
13865
13866
13867 /*
13868  * This is necessary because some C libraries aren't ANSI C compliant yet.
13869  */
13870 char *
13871 StrStr(string, match)
13872      char *string, *match;
13873 {
13874     int i, length;
13875     
13876     length = strlen(match);
13877     
13878     for (i = strlen(string) - length; i >= 0; i--, string++)
13879       if (!strncmp(match, string, length))
13880         return string;
13881     
13882     return NULL;
13883 }
13884
13885 char *
13886 StrCaseStr(string, match)
13887      char *string, *match;
13888 {
13889     int i, j, length;
13890     
13891     length = strlen(match);
13892     
13893     for (i = strlen(string) - length; i >= 0; i--, string++) {
13894         for (j = 0; j < length; j++) {
13895             if (ToLower(match[j]) != ToLower(string[j]))
13896               break;
13897         }
13898         if (j == length) return string;
13899     }
13900
13901     return NULL;
13902 }
13903
13904 #ifndef _amigados
13905 int
13906 StrCaseCmp(s1, s2)
13907      char *s1, *s2;
13908 {
13909     char c1, c2;
13910     
13911     for (;;) {
13912         c1 = ToLower(*s1++);
13913         c2 = ToLower(*s2++);
13914         if (c1 > c2) return 1;
13915         if (c1 < c2) return -1;
13916         if (c1 == NULLCHAR) return 0;
13917     }
13918 }
13919
13920
13921 int
13922 ToLower(c)
13923      int c;
13924 {
13925     return isupper(c) ? tolower(c) : c;
13926 }
13927
13928
13929 int
13930 ToUpper(c)
13931      int c;
13932 {
13933     return islower(c) ? toupper(c) : c;
13934 }
13935 #endif /* !_amigados    */
13936
13937 char *
13938 StrSave(s)
13939      char *s;
13940 {
13941     char *ret;
13942
13943     if ((ret = (char *) malloc(strlen(s) + 1))) {
13944         strcpy(ret, s);
13945     }
13946     return ret;
13947 }
13948
13949 char *
13950 StrSavePtr(s, savePtr)
13951      char *s, **savePtr;
13952 {
13953     if (*savePtr) {
13954         free(*savePtr);
13955     }
13956     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13957         strcpy(*savePtr, s);
13958     }
13959     return(*savePtr);
13960 }
13961
13962 char *
13963 PGNDate()
13964 {
13965     time_t clock;
13966     struct tm *tm;
13967     char buf[MSG_SIZ];
13968
13969     clock = time((time_t *)NULL);
13970     tm = localtime(&clock);
13971     sprintf(buf, "%04d.%02d.%02d",
13972             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13973     return StrSave(buf);
13974 }
13975
13976
13977 char *
13978 PositionToFEN(move, overrideCastling)
13979      int move;
13980      char *overrideCastling;
13981 {
13982     int i, j, fromX, fromY, toX, toY;
13983     int whiteToPlay;
13984     char buf[128];
13985     char *p, *q;
13986     int emptycount;
13987     ChessSquare piece;
13988
13989     whiteToPlay = (gameMode == EditPosition) ?
13990       !blackPlaysFirst : (move % 2 == 0);
13991     p = buf;
13992
13993     /* Piece placement data */
13994     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13995         emptycount = 0;
13996         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13997             if (boards[move][i][j] == EmptySquare) {
13998                 emptycount++;
13999             } else { ChessSquare piece = boards[move][i][j];
14000                 if (emptycount > 0) {
14001                     if(emptycount<10) /* [HGM] can be >= 10 */
14002                         *p++ = '0' + emptycount;
14003                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14004                     emptycount = 0;
14005                 }
14006                 if(PieceToChar(piece) == '+') {
14007                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14008                     *p++ = '+';
14009                     piece = (ChessSquare)(DEMOTED piece);
14010                 } 
14011                 *p++ = PieceToChar(piece);
14012                 if(p[-1] == '~') {
14013                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14014                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14015                     *p++ = '~';
14016                 }
14017             }
14018         }
14019         if (emptycount > 0) {
14020             if(emptycount<10) /* [HGM] can be >= 10 */
14021                 *p++ = '0' + emptycount;
14022             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14023             emptycount = 0;
14024         }
14025         *p++ = '/';
14026     }
14027     *(p - 1) = ' ';
14028
14029     /* [HGM] print Crazyhouse or Shogi holdings */
14030     if( gameInfo.holdingsWidth ) {
14031         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14032         q = p;
14033         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14034             piece = boards[move][i][BOARD_WIDTH-1];
14035             if( piece != EmptySquare )
14036               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14037                   *p++ = PieceToChar(piece);
14038         }
14039         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14040             piece = boards[move][BOARD_HEIGHT-i-1][0];
14041             if( piece != EmptySquare )
14042               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14043                   *p++ = PieceToChar(piece);
14044         }
14045
14046         if( q == p ) *p++ = '-';
14047         *p++ = ']';
14048         *p++ = ' ';
14049     }
14050
14051     /* Active color */
14052     *p++ = whiteToPlay ? 'w' : 'b';
14053     *p++ = ' ';
14054
14055   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14056     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14057   } else {
14058   if(nrCastlingRights) {
14059      q = p;
14060      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14061        /* [HGM] write directly from rights */
14062            if(boards[move][CASTLING][2] != NoRights &&
14063               boards[move][CASTLING][0] != NoRights   )
14064                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14065            if(boards[move][CASTLING][2] != NoRights &&
14066               boards[move][CASTLING][1] != NoRights   )
14067                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14068            if(boards[move][CASTLING][5] != NoRights &&
14069               boards[move][CASTLING][3] != NoRights   )
14070                 *p++ = boards[move][CASTLING][3] + AAA;
14071            if(boards[move][CASTLING][5] != NoRights &&
14072               boards[move][CASTLING][4] != NoRights   )
14073                 *p++ = boards[move][CASTLING][4] + AAA;
14074      } else {
14075
14076         /* [HGM] write true castling rights */
14077         if( nrCastlingRights == 6 ) {
14078             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14079                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14080             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14081                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14082             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14083                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14084             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14085                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14086         }
14087      }
14088      if (q == p) *p++ = '-'; /* No castling rights */
14089      *p++ = ' ';
14090   }
14091
14092   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14093      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14094     /* En passant target square */
14095     if (move > backwardMostMove) {
14096         fromX = moveList[move - 1][0] - AAA;
14097         fromY = moveList[move - 1][1] - ONE;
14098         toX = moveList[move - 1][2] - AAA;
14099         toY = moveList[move - 1][3] - ONE;
14100         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14101             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14102             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14103             fromX == toX) {
14104             /* 2-square pawn move just happened */
14105             *p++ = toX + AAA;
14106             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14107         } else {
14108             *p++ = '-';
14109         }
14110     } else if(move == backwardMostMove) {
14111         // [HGM] perhaps we should always do it like this, and forget the above?
14112         if((signed char)boards[move][EP_STATUS] >= 0) {
14113             *p++ = boards[move][EP_STATUS] + AAA;
14114             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14115         } else {
14116             *p++ = '-';
14117         }
14118     } else {
14119         *p++ = '-';
14120     }
14121     *p++ = ' ';
14122   }
14123   }
14124
14125     /* [HGM] find reversible plies */
14126     {   int i = 0, j=move;
14127
14128         if (appData.debugMode) { int k;
14129             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14130             for(k=backwardMostMove; k<=forwardMostMove; k++)
14131                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14132
14133         }
14134
14135         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14136         if( j == backwardMostMove ) i += initialRulePlies;
14137         sprintf(p, "%d ", i);
14138         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14139     }
14140     /* Fullmove number */
14141     sprintf(p, "%d", (move / 2) + 1);
14142     
14143     return StrSave(buf);
14144 }
14145
14146 Boolean
14147 ParseFEN(board, blackPlaysFirst, fen)
14148     Board board;
14149      int *blackPlaysFirst;
14150      char *fen;
14151 {
14152     int i, j;
14153     char *p;
14154     int emptycount;
14155     ChessSquare piece;
14156
14157     p = fen;
14158
14159     /* [HGM] by default clear Crazyhouse holdings, if present */
14160     if(gameInfo.holdingsWidth) {
14161        for(i=0; i<BOARD_HEIGHT; i++) {
14162            board[i][0]             = EmptySquare; /* black holdings */
14163            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14164            board[i][1]             = (ChessSquare) 0; /* black counts */
14165            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14166        }
14167     }
14168
14169     /* Piece placement data */
14170     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14171         j = 0;
14172         for (;;) {
14173             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14174                 if (*p == '/') p++;
14175                 emptycount = gameInfo.boardWidth - j;
14176                 while (emptycount--)
14177                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14178                 break;
14179 #if(BOARD_FILES >= 10)
14180             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14181                 p++; emptycount=10;
14182                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14183                 while (emptycount--)
14184                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14185 #endif
14186             } else if (isdigit(*p)) {
14187                 emptycount = *p++ - '0';
14188                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14189                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14190                 while (emptycount--)
14191                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14192             } else if (*p == '+' || isalpha(*p)) {
14193                 if (j >= gameInfo.boardWidth) return FALSE;
14194                 if(*p=='+') {
14195                     piece = CharToPiece(*++p);
14196                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14197                     piece = (ChessSquare) (PROMOTED piece ); p++;
14198                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14199                 } else piece = CharToPiece(*p++);
14200
14201                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14202                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14203                     piece = (ChessSquare) (PROMOTED piece);
14204                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14205                     p++;
14206                 }
14207                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14208             } else {
14209                 return FALSE;
14210             }
14211         }
14212     }
14213     while (*p == '/' || *p == ' ') p++;
14214
14215     /* [HGM] look for Crazyhouse holdings here */
14216     while(*p==' ') p++;
14217     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14218         if(*p == '[') p++;
14219         if(*p == '-' ) *p++; /* empty holdings */ else {
14220             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14221             /* if we would allow FEN reading to set board size, we would   */
14222             /* have to add holdings and shift the board read so far here   */
14223             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14224                 *p++;
14225                 if((int) piece >= (int) BlackPawn ) {
14226                     i = (int)piece - (int)BlackPawn;
14227                     i = PieceToNumber((ChessSquare)i);
14228                     if( i >= gameInfo.holdingsSize ) return FALSE;
14229                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14230                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14231                 } else {
14232                     i = (int)piece - (int)WhitePawn;
14233                     i = PieceToNumber((ChessSquare)i);
14234                     if( i >= gameInfo.holdingsSize ) return FALSE;
14235                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14236                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14237                 }
14238             }
14239         }
14240         if(*p == ']') *p++;
14241     }
14242
14243     while(*p == ' ') p++;
14244
14245     /* Active color */
14246     switch (*p++) {
14247       case 'w':
14248         *blackPlaysFirst = FALSE;
14249         break;
14250       case 'b': 
14251         *blackPlaysFirst = TRUE;
14252         break;
14253       default:
14254         return FALSE;
14255     }
14256
14257     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14258     /* return the extra info in global variiables             */
14259
14260     /* set defaults in case FEN is incomplete */
14261     board[EP_STATUS] = EP_UNKNOWN;
14262     for(i=0; i<nrCastlingRights; i++ ) {
14263         board[CASTLING][i] =
14264             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14265     }   /* assume possible unless obviously impossible */
14266     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14267     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14268     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14269                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14270     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14271     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14272     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14273                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14274     FENrulePlies = 0;
14275
14276     while(*p==' ') p++;
14277     if(nrCastlingRights) {
14278       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14279           /* castling indicator present, so default becomes no castlings */
14280           for(i=0; i<nrCastlingRights; i++ ) {
14281                  board[CASTLING][i] = NoRights;
14282           }
14283       }
14284       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14285              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14286              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14287              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14288         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14289
14290         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14291             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14292             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14293         }
14294         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14295             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14296         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14297                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14298         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14299                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14300         switch(c) {
14301           case'K':
14302               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14303               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14304               board[CASTLING][2] = whiteKingFile;
14305               break;
14306           case'Q':
14307               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14308               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14309               board[CASTLING][2] = whiteKingFile;
14310               break;
14311           case'k':
14312               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14313               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14314               board[CASTLING][5] = blackKingFile;
14315               break;
14316           case'q':
14317               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14318               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14319               board[CASTLING][5] = blackKingFile;
14320           case '-':
14321               break;
14322           default: /* FRC castlings */
14323               if(c >= 'a') { /* black rights */
14324                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14325                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14326                   if(i == BOARD_RGHT) break;
14327                   board[CASTLING][5] = i;
14328                   c -= AAA;
14329                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14330                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14331                   if(c > i)
14332                       board[CASTLING][3] = c;
14333                   else
14334                       board[CASTLING][4] = c;
14335               } else { /* white rights */
14336                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14337                     if(board[0][i] == WhiteKing) break;
14338                   if(i == BOARD_RGHT) break;
14339                   board[CASTLING][2] = i;
14340                   c -= AAA - 'a' + 'A';
14341                   if(board[0][c] >= WhiteKing) break;
14342                   if(c > i)
14343                       board[CASTLING][0] = c;
14344                   else
14345                       board[CASTLING][1] = c;
14346               }
14347         }
14348       }
14349       for(i=0; i<nrCastlingRights; i++)
14350         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14351     if (appData.debugMode) {
14352         fprintf(debugFP, "FEN castling rights:");
14353         for(i=0; i<nrCastlingRights; i++)
14354         fprintf(debugFP, " %d", board[CASTLING][i]);
14355         fprintf(debugFP, "\n");
14356     }
14357
14358       while(*p==' ') p++;
14359     }
14360
14361     /* read e.p. field in games that know e.p. capture */
14362     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14363        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14364       if(*p=='-') {
14365         p++; board[EP_STATUS] = EP_NONE;
14366       } else {
14367          char c = *p++ - AAA;
14368
14369          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14370          if(*p >= '0' && *p <='9') *p++;
14371          board[EP_STATUS] = c;
14372       }
14373     }
14374
14375
14376     if(sscanf(p, "%d", &i) == 1) {
14377         FENrulePlies = i; /* 50-move ply counter */
14378         /* (The move number is still ignored)    */
14379     }
14380
14381     return TRUE;
14382 }
14383       
14384 void
14385 EditPositionPasteFEN(char *fen)
14386 {
14387   if (fen != NULL) {
14388     Board initial_position;
14389
14390     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14391       DisplayError(_("Bad FEN position in clipboard"), 0);
14392       return ;
14393     } else {
14394       int savedBlackPlaysFirst = blackPlaysFirst;
14395       EditPositionEvent();
14396       blackPlaysFirst = savedBlackPlaysFirst;
14397       CopyBoard(boards[0], initial_position);
14398       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14399       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14400       DisplayBothClocks();
14401       DrawPosition(FALSE, boards[currentMove]);
14402     }
14403   }
14404 }
14405
14406 static char cseq[12] = "\\   ";
14407
14408 Boolean set_cont_sequence(char *new_seq)
14409 {
14410     int len;
14411     Boolean ret;
14412
14413     // handle bad attempts to set the sequence
14414         if (!new_seq)
14415                 return 0; // acceptable error - no debug
14416
14417     len = strlen(new_seq);
14418     ret = (len > 0) && (len < sizeof(cseq));
14419     if (ret)
14420         strcpy(cseq, new_seq);
14421     else if (appData.debugMode)
14422         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14423     return ret;
14424 }
14425
14426 /*
14427     reformat a source message so words don't cross the width boundary.  internal
14428     newlines are not removed.  returns the wrapped size (no null character unless
14429     included in source message).  If dest is NULL, only calculate the size required
14430     for the dest buffer.  lp argument indicats line position upon entry, and it's
14431     passed back upon exit.
14432 */
14433 int wrap(char *dest, char *src, int count, int width, int *lp)
14434 {
14435     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14436
14437     cseq_len = strlen(cseq);
14438     old_line = line = *lp;
14439     ansi = len = clen = 0;
14440
14441     for (i=0; i < count; i++)
14442     {
14443         if (src[i] == '\033')
14444             ansi = 1;
14445
14446         // if we hit the width, back up
14447         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14448         {
14449             // store i & len in case the word is too long
14450             old_i = i, old_len = len;
14451
14452             // find the end of the last word
14453             while (i && src[i] != ' ' && src[i] != '\n')
14454             {
14455                 i--;
14456                 len--;
14457             }
14458
14459             // word too long?  restore i & len before splitting it
14460             if ((old_i-i+clen) >= width)
14461             {
14462                 i = old_i;
14463                 len = old_len;
14464             }
14465
14466             // extra space?
14467             if (i && src[i-1] == ' ')
14468                 len--;
14469
14470             if (src[i] != ' ' && src[i] != '\n')
14471             {
14472                 i--;
14473                 if (len)
14474                     len--;
14475             }
14476
14477             // now append the newline and continuation sequence
14478             if (dest)
14479                 dest[len] = '\n';
14480             len++;
14481             if (dest)
14482                 strncpy(dest+len, cseq, cseq_len);
14483             len += cseq_len;
14484             line = cseq_len;
14485             clen = cseq_len;
14486             continue;
14487         }
14488
14489         if (dest)
14490             dest[len] = src[i];
14491         len++;
14492         if (!ansi)
14493             line++;
14494         if (src[i] == '\n')
14495             line = 0;
14496         if (src[i] == 'm')
14497             ansi = 0;
14498     }
14499     if (dest && appData.debugMode)
14500     {
14501         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14502             count, width, line, len, *lp);
14503         show_bytes(debugFP, src, count);
14504         fprintf(debugFP, "\ndest: ");
14505         show_bytes(debugFP, dest, len);
14506         fprintf(debugFP, "\n");
14507     }
14508     *lp = dest ? line : old_line;
14509
14510     return len;
14511 }
14512
14513 // [HGM] vari: routines for shelving variations
14514
14515 void 
14516 PushTail(int firstMove, int lastMove)
14517 {
14518         int i, j, nrMoves = lastMove - firstMove;
14519
14520         if(appData.icsActive) { // only in local mode
14521                 forwardMostMove = currentMove; // mimic old ICS behavior
14522                 return;
14523         }
14524         if(storedGames >= MAX_VARIATIONS-1) return;
14525
14526         // push current tail of game on stack
14527         savedResult[storedGames] = gameInfo.result;
14528         savedDetails[storedGames] = gameInfo.resultDetails;
14529         gameInfo.resultDetails = NULL;
14530         savedFirst[storedGames] = firstMove;
14531         savedLast [storedGames] = lastMove;
14532         savedFramePtr[storedGames] = framePtr;
14533         framePtr -= nrMoves; // reserve space for the boards
14534         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14535             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14536             for(j=0; j<MOVE_LEN; j++)
14537                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14538             for(j=0; j<2*MOVE_LEN; j++)
14539                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14540             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14541             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14542             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14543             pvInfoList[firstMove+i-1].depth = 0;
14544             commentList[framePtr+i] = commentList[firstMove+i];
14545             commentList[firstMove+i] = NULL;
14546         }
14547
14548         storedGames++;
14549         forwardMostMove = currentMove; // truncte game so we can start variation
14550         if(storedGames == 1) GreyRevert(FALSE);
14551 }
14552
14553 Boolean
14554 PopTail(Boolean annotate)
14555 {
14556         int i, j, nrMoves;
14557         char buf[8000], moveBuf[20];
14558
14559         if(appData.icsActive) return FALSE; // only in local mode
14560         if(!storedGames) return FALSE; // sanity
14561
14562         storedGames--;
14563         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14564         nrMoves = savedLast[storedGames] - currentMove;
14565         if(annotate) {
14566                 int cnt = 10;
14567                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14568                 else strcpy(buf, "(");
14569                 for(i=currentMove; i<forwardMostMove; i++) {
14570                         if(WhiteOnMove(i))
14571                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14572                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14573                         strcat(buf, moveBuf);
14574                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14575                 }
14576                 strcat(buf, ")");
14577         }
14578         for(i=1; i<nrMoves; i++) { // copy last variation back
14579             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14580             for(j=0; j<MOVE_LEN; j++)
14581                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14582             for(j=0; j<2*MOVE_LEN; j++)
14583                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14584             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14585             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14586             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14587             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14588             commentList[currentMove+i] = commentList[framePtr+i];
14589             commentList[framePtr+i] = NULL;
14590         }
14591         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14592         framePtr = savedFramePtr[storedGames];
14593         gameInfo.result = savedResult[storedGames];
14594         if(gameInfo.resultDetails != NULL) {
14595             free(gameInfo.resultDetails);
14596       }
14597         gameInfo.resultDetails = savedDetails[storedGames];
14598         forwardMostMove = currentMove + nrMoves;
14599         if(storedGames == 0) GreyRevert(TRUE);
14600         return TRUE;
14601 }
14602
14603 void 
14604 CleanupTail()
14605 {       // remove all shelved variations
14606         int i;
14607         for(i=0; i<storedGames; i++) {
14608             if(savedDetails[i])
14609                 free(savedDetails[i]);
14610             savedDetails[i] = NULL;
14611         }
14612         for(i=framePtr; i<MAX_MOVES; i++) {
14613                 if(commentList[i]) free(commentList[i]);
14614                 commentList[i] = NULL;
14615         }
14616         framePtr = MAX_MOVES-1;
14617         storedGames = 0;
14618 }