a9b29e49d15d56a42923ebccc1651e1b22bd18f1
[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 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5945 {
5946 //    char * hint = lastHint;
5947     FrontEndProgramStats stats;
5948
5949     stats.which = cps == &first ? 0 : 1;
5950     stats.depth = cpstats->depth;
5951     stats.nodes = cpstats->nodes;
5952     stats.score = cpstats->score;
5953     stats.time = cpstats->time;
5954     stats.pv = cpstats->movelist;
5955     stats.hint = lastHint;
5956     stats.an_move_index = 0;
5957     stats.an_move_count = 0;
5958
5959     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5960         stats.hint = cpstats->move_name;
5961         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5962         stats.an_move_count = cpstats->nr_moves;
5963     }
5964
5965     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5966
5967     SetProgramStats( &stats );
5968 }
5969
5970 int
5971 Adjudicate(ChessProgramState *cps)
5972 {       // [HGM] some adjudications useful with buggy engines
5973         // [HGM] adjudicate: made into separate routine, which now can be called after every move
5974         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
5975         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
5976         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
5977         int k, count = 0; static int bare = 1;
5978         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
5979         Boolean canAdjudicate = !appData.icsActive;
5980
5981         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
5982         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5983             if( appData.testLegality )
5984             {   /* [HGM] Some more adjudications for obstinate engines */
5985                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5986                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5987                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5988                 static int moveCount = 6;
5989                 ChessMove result;
5990                 char *reason = NULL;
5991
5992                 /* Count what is on board. */
5993                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5994                 {   ChessSquare p = boards[forwardMostMove][i][j];
5995                     int m=i;
5996
5997                     switch((int) p)
5998                     {   /* count B,N,R and other of each side */
5999                         case WhiteKing:
6000                         case BlackKing:
6001                              NrK++; break; // [HGM] atomic: count Kings
6002                         case WhiteKnight:
6003                              NrWN++; break;
6004                         case WhiteBishop:
6005                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6006                              bishopsColor |= 1 << ((i^j)&1);
6007                              NrWB++; break;
6008                         case BlackKnight:
6009                              NrBN++; break;
6010                         case BlackBishop:
6011                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6012                              bishopsColor |= 1 << ((i^j)&1);
6013                              NrBB++; break;
6014                         case WhiteRook:
6015                              NrWR++; break;
6016                         case BlackRook:
6017                              NrBR++; break;
6018                         case WhiteQueen:
6019                              NrWQ++; break;
6020                         case BlackQueen:
6021                              NrBQ++; break;
6022                         case EmptySquare: 
6023                              break;
6024                         case BlackPawn:
6025                              m = 7-i;
6026                         case WhitePawn:
6027                              PawnAdvance += m; NrPawns++;
6028                     }
6029                     NrPieces += (p != EmptySquare);
6030                     NrW += ((int)p < (int)BlackPawn);
6031                     if(gameInfo.variant == VariantXiangqi && 
6032                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6033                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6034                         NrW -= ((int)p < (int)BlackPawn);
6035                     }
6036                 }
6037
6038                 /* Some material-based adjudications that have to be made before stalemate test */
6039                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6040                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6041                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6042                      if(canAdjudicate && appData.checkMates) {
6043                          if(engineOpponent)
6044                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6045                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6046                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6047                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6048                          return 1;
6049                      }
6050                 }
6051
6052                 /* Bare King in Shatranj (loses) or Losers (wins) */
6053                 if( NrW == 1 || NrPieces - NrW == 1) {
6054                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6055                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6056                      if(canAdjudicate && appData.checkMates) {
6057                          if(engineOpponent)
6058                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6059                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6060                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6061                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6062                          return 1;
6063                      }
6064                   } else
6065                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6066                   {    /* bare King */
6067                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6068                         if(canAdjudicate && appData.checkMates) {
6069                             /* but only adjudicate if adjudication enabled */
6070                             if(engineOpponent)
6071                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6072                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6073                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6074                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6075                             return 1;
6076                         }
6077                   }
6078                 } else bare = 1;
6079
6080
6081             // don't wait for engine to announce game end if we can judge ourselves
6082             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6083               case MT_CHECK:
6084                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6085                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6086                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6087                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6088                             checkCnt++;
6089                         if(checkCnt >= 2) {
6090                             reason = "Xboard adjudication: 3rd check";
6091                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6092                             break;
6093                         }
6094                     }
6095                 }
6096               case MT_NONE:
6097               default:
6098                 break;
6099               case MT_STALEMATE:
6100               case MT_STAINMATE:
6101                 reason = "Xboard adjudication: Stalemate";
6102                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6103                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6104                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6105                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6106                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6107                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6108                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6109                                                                         EP_CHECKMATE : EP_WINS);
6110                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6111                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6112                 }
6113                 break;
6114               case MT_CHECKMATE:
6115                 reason = "Xboard adjudication: Checkmate";
6116                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6117                 break;
6118             }
6119
6120                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6121                     case EP_STALEMATE:
6122                         result = GameIsDrawn; break;
6123                     case EP_CHECKMATE:
6124                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6125                     case EP_WINS:
6126                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6127                     default:
6128                         result = (ChessMove) 0;
6129                 }
6130                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6131                     if(engineOpponent)
6132                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6133                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6134                     GameEnds( result, reason, GE_XBOARD );
6135                     return 1;
6136                 }
6137
6138                 /* Next absolutely insufficient mating material. */
6139                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6140                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6141                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6142                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6143                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6144
6145                      /* always flag draws, for judging claims */
6146                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6147
6148                      if(canAdjudicate && appData.materialDraws) {
6149                          /* but only adjudicate them if adjudication enabled */
6150                          if(engineOpponent) {
6151                            SendToProgram("force\n", engineOpponent); // suppress reply
6152                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6153                          }
6154                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6155                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6156                          return 1;
6157                      }
6158                 }
6159
6160                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6161                 if(NrPieces == 4 && 
6162                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6163                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6164                    || NrWN==2 || NrBN==2     /* KNNK */
6165                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6166                   ) ) {
6167                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6168                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6169                           if(engineOpponent) {
6170                             SendToProgram("force\n", engineOpponent); // suppress reply
6171                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6172                           }
6173                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6174                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6175                           return 1;
6176                      }
6177                 } else moveCount = 6;
6178             }
6179         }
6180           
6181         if (appData.debugMode) { int i;
6182             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6183                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6184                     appData.drawRepeats);
6185             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6186               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6187             
6188         }
6189
6190         // Repetition draws and 50-move rule can be applied independently of legality testing
6191
6192                 /* Check for rep-draws */
6193                 count = 0;
6194                 for(k = forwardMostMove-2;
6195                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6196                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6197                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6198                     k-=2)
6199                 {   int rights=0;
6200                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6201                         /* compare castling rights */
6202                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6203                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6204                                 rights++; /* King lost rights, while rook still had them */
6205                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6206                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6207                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6208                                    rights++; /* but at least one rook lost them */
6209                         }
6210                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6211                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6212                                 rights++; 
6213                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6214                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6215                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6216                                    rights++;
6217                         }
6218                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6219                             && appData.drawRepeats > 1) {
6220                              /* adjudicate after user-specified nr of repeats */
6221                              if(engineOpponent) {
6222                                SendToProgram("force\n", engineOpponent); // suppress reply
6223                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6224                              }
6225                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6226                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6227                                 // [HGM] xiangqi: check for forbidden perpetuals
6228                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6229                                 for(m=forwardMostMove; m>k; m-=2) {
6230                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6231                                         ourPerpetual = 0; // the current mover did not always check
6232                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6233                                         hisPerpetual = 0; // the opponent did not always check
6234                                 }
6235                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6236                                                                         ourPerpetual, hisPerpetual);
6237                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6238                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6239                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6240                                     return 1;
6241                                 }
6242                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6243                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6244                                 // Now check for perpetual chases
6245                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6246                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6247                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6248                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6249                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6250                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6251                                         return 1;
6252                                     }
6253                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6254                                         break; // Abort repetition-checking loop.
6255                                 }
6256                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6257                              }
6258                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6259                              return 1;
6260                         }
6261                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6262                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6263                     }
6264                 }
6265
6266                 /* Now we test for 50-move draws. Determine ply count */
6267                 count = forwardMostMove;
6268                 /* look for last irreversble move */
6269                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6270                     count--;
6271                 /* if we hit starting position, add initial plies */
6272                 if( count == backwardMostMove )
6273                     count -= initialRulePlies;
6274                 count = forwardMostMove - count; 
6275                 if( count >= 100)
6276                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6277                          /* this is used to judge if draw claims are legal */
6278                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6279                          if(engineOpponent) {
6280                            SendToProgram("force\n", engineOpponent); // suppress reply
6281                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6282                          }
6283                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6284                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6285                          return 1;
6286                 }
6287
6288                 /* if draw offer is pending, treat it as a draw claim
6289                  * when draw condition present, to allow engines a way to
6290                  * claim draws before making their move to avoid a race
6291                  * condition occurring after their move
6292                  */
6293                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6294                          char *p = NULL;
6295                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6296                              p = "Draw claim: 50-move rule";
6297                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6298                              p = "Draw claim: 3-fold repetition";
6299                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6300                              p = "Draw claim: insufficient mating material";
6301                          if( p != NULL && canAdjudicate) {
6302                              if(engineOpponent) {
6303                                SendToProgram("force\n", engineOpponent); // suppress reply
6304                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6305                              }
6306                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6307                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6308                              return 1;
6309                          }
6310                 }
6311
6312                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6313                     if(engineOpponent) {
6314                       SendToProgram("force\n", engineOpponent); // suppress reply
6315                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6316                     }
6317                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6318                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6319                     return 1;
6320                 }
6321         return 0;
6322 }
6323
6324 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6325 {   // [HGM] book: this routine intercepts moves to simulate book replies
6326     char *bookHit = NULL;
6327
6328     //first determine if the incoming move brings opponent into his book
6329     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6330         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6331     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6332     if(bookHit != NULL && !cps->bookSuspend) {
6333         // make sure opponent is not going to reply after receiving move to book position
6334         SendToProgram("force\n", cps);
6335         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6336     }
6337     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6338     // now arrange restart after book miss
6339     if(bookHit) {
6340         // after a book hit we never send 'go', and the code after the call to this routine
6341         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6342         char buf[MSG_SIZ];
6343         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6344         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6345         SendToProgram(buf, cps);
6346         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6347     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6348         SendToProgram("go\n", cps);
6349         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6350     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6351         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6352             SendToProgram("go\n", cps); 
6353         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6354     }
6355     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6356 }
6357
6358 char *savedMessage;
6359 ChessProgramState *savedState;
6360 void DeferredBookMove(void)
6361 {
6362         if(savedState->lastPing != savedState->lastPong)
6363                     ScheduleDelayedEvent(DeferredBookMove, 10);
6364         else
6365         HandleMachineMove(savedMessage, savedState);
6366 }
6367
6368 void
6369 HandleMachineMove(message, cps)
6370      char *message;
6371      ChessProgramState *cps;
6372 {
6373     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6374     char realname[MSG_SIZ];
6375     int fromX, fromY, toX, toY;
6376     ChessMove moveType;
6377     char promoChar;
6378     char *p;
6379     int machineWhite;
6380     char *bookHit;
6381
6382     cps->userError = 0;
6383
6384 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6385     /*
6386      * Kludge to ignore BEL characters
6387      */
6388     while (*message == '\007') message++;
6389
6390     /*
6391      * [HGM] engine debug message: ignore lines starting with '#' character
6392      */
6393     if(cps->debug && *message == '#') return;
6394
6395     /*
6396      * Look for book output
6397      */
6398     if (cps == &first && bookRequested) {
6399         if (message[0] == '\t' || message[0] == ' ') {
6400             /* Part of the book output is here; append it */
6401             strcat(bookOutput, message);
6402             strcat(bookOutput, "  \n");
6403             return;
6404         } else if (bookOutput[0] != NULLCHAR) {
6405             /* All of book output has arrived; display it */
6406             char *p = bookOutput;
6407             while (*p != NULLCHAR) {
6408                 if (*p == '\t') *p = ' ';
6409                 p++;
6410             }
6411             DisplayInformation(bookOutput);
6412             bookRequested = FALSE;
6413             /* Fall through to parse the current output */
6414         }
6415     }
6416
6417     /*
6418      * Look for machine move.
6419      */
6420     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6421         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6422     {
6423         /* This method is only useful on engines that support ping */
6424         if (cps->lastPing != cps->lastPong) {
6425           if (gameMode == BeginningOfGame) {
6426             /* Extra move from before last new; ignore */
6427             if (appData.debugMode) {
6428                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6429             }
6430           } else {
6431             if (appData.debugMode) {
6432                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6433                         cps->which, gameMode);
6434             }
6435
6436             SendToProgram("undo\n", cps);
6437           }
6438           return;
6439         }
6440
6441         switch (gameMode) {
6442           case BeginningOfGame:
6443             /* Extra move from before last reset; ignore */
6444             if (appData.debugMode) {
6445                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6446             }
6447             return;
6448
6449           case EndOfGame:
6450           case IcsIdle:
6451           default:
6452             /* Extra move after we tried to stop.  The mode test is
6453                not a reliable way of detecting this problem, but it's
6454                the best we can do on engines that don't support ping.
6455             */
6456             if (appData.debugMode) {
6457                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6458                         cps->which, gameMode);
6459             }
6460             SendToProgram("undo\n", cps);
6461             return;
6462
6463           case MachinePlaysWhite:
6464           case IcsPlayingWhite:
6465             machineWhite = TRUE;
6466             break;
6467
6468           case MachinePlaysBlack:
6469           case IcsPlayingBlack:
6470             machineWhite = FALSE;
6471             break;
6472
6473           case TwoMachinesPlay:
6474             machineWhite = (cps->twoMachinesColor[0] == 'w');
6475             break;
6476         }
6477         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6478             if (appData.debugMode) {
6479                 fprintf(debugFP,
6480                         "Ignoring move out of turn by %s, gameMode %d"
6481                         ", forwardMost %d\n",
6482                         cps->which, gameMode, forwardMostMove);
6483             }
6484             return;
6485         }
6486
6487     if (appData.debugMode) { int f = forwardMostMove;
6488         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6489                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6490                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6491     }
6492         if(cps->alphaRank) AlphaRank(machineMove, 4);
6493         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6494                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6495             /* Machine move could not be parsed; ignore it. */
6496             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6497                     machineMove, cps->which);
6498             DisplayError(buf1, 0);
6499             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6500                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6501             if (gameMode == TwoMachinesPlay) {
6502               GameEnds(machineWhite ? BlackWins : WhiteWins,
6503                        buf1, GE_XBOARD);
6504             }
6505             return;
6506         }
6507
6508         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6509         /* So we have to redo legality test with true e.p. status here,  */
6510         /* to make sure an illegal e.p. capture does not slip through,   */
6511         /* to cause a forfeit on a justified illegal-move complaint      */
6512         /* of the opponent.                                              */
6513         if( gameMode==TwoMachinesPlay && appData.testLegality
6514             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6515                                                               ) {
6516            ChessMove moveType;
6517            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6518                              fromY, fromX, toY, toX, promoChar);
6519             if (appData.debugMode) {
6520                 int i;
6521                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6522                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6523                 fprintf(debugFP, "castling rights\n");
6524             }
6525             if(moveType == IllegalMove) {
6526                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6527                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6528                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6529                            buf1, GE_XBOARD);
6530                 return;
6531            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6532            /* [HGM] Kludge to handle engines that send FRC-style castling
6533               when they shouldn't (like TSCP-Gothic) */
6534            switch(moveType) {
6535              case WhiteASideCastleFR:
6536              case BlackASideCastleFR:
6537                toX+=2;
6538                currentMoveString[2]++;
6539                break;
6540              case WhiteHSideCastleFR:
6541              case BlackHSideCastleFR:
6542                toX--;
6543                currentMoveString[2]--;
6544                break;
6545              default: ; // nothing to do, but suppresses warning of pedantic compilers
6546            }
6547         }
6548         hintRequested = FALSE;
6549         lastHint[0] = NULLCHAR;
6550         bookRequested = FALSE;
6551         /* Program may be pondering now */
6552         cps->maybeThinking = TRUE;
6553         if (cps->sendTime == 2) cps->sendTime = 1;
6554         if (cps->offeredDraw) cps->offeredDraw--;
6555
6556         /* currentMoveString is set as a side-effect of ParseOneMove */
6557         strcpy(machineMove, currentMoveString);
6558         strcat(machineMove, "\n");
6559         strcpy(moveList[forwardMostMove], machineMove);
6560
6561         /* [AS] Save move info and clear stats for next move */
6562         pvInfoList[ forwardMostMove ].score = programStats.score;
6563         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6564         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6565         ClearProgramStats();
6566         thinkOutput[0] = NULLCHAR;
6567         hiddenThinkOutputState = 0;
6568
6569         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6570
6571         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6572         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6573             int count = 0;
6574
6575             while( count < adjudicateLossPlies ) {
6576                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6577
6578                 if( count & 1 ) {
6579                     score = -score; /* Flip score for winning side */
6580                 }
6581
6582                 if( score > adjudicateLossThreshold ) {
6583                     break;
6584                 }
6585
6586                 count++;
6587             }
6588
6589             if( count >= adjudicateLossPlies ) {
6590                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6591
6592                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6593                     "Xboard adjudication", 
6594                     GE_XBOARD );
6595
6596                 return;
6597             }
6598         }
6599
6600         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6601
6602 #if ZIPPY
6603         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6604             first.initDone) {
6605           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6606                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6607                 SendToICS("draw ");
6608                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6609           }
6610           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6611           ics_user_moved = 1;
6612           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6613                 char buf[3*MSG_SIZ];
6614
6615                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6616                         programStats.score / 100.,
6617                         programStats.depth,
6618                         programStats.time / 100.,
6619                         (unsigned int)programStats.nodes,
6620                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6621                         programStats.movelist);
6622                 SendToICS(buf);
6623 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6624           }
6625         }
6626 #endif
6627
6628         bookHit = NULL;
6629         if (gameMode == TwoMachinesPlay) {
6630             /* [HGM] relaying draw offers moved to after reception of move */
6631             /* and interpreting offer as claim if it brings draw condition */
6632             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6633                 SendToProgram("draw\n", cps->other);
6634             }
6635             if (cps->other->sendTime) {
6636                 SendTimeRemaining(cps->other,
6637                                   cps->other->twoMachinesColor[0] == 'w');
6638             }
6639             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6640             if (firstMove && !bookHit) {
6641                 firstMove = FALSE;
6642                 if (cps->other->useColors) {
6643                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6644                 }
6645                 SendToProgram("go\n", cps->other);
6646             }
6647             cps->other->maybeThinking = TRUE;
6648         }
6649
6650         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6651         
6652         if (!pausing && appData.ringBellAfterMoves) {
6653             RingBell();
6654         }
6655
6656         /* 
6657          * Reenable menu items that were disabled while
6658          * machine was thinking
6659          */
6660         if (gameMode != TwoMachinesPlay)
6661             SetUserThinkingEnables();
6662
6663         // [HGM] book: after book hit opponent has received move and is now in force mode
6664         // force the book reply into it, and then fake that it outputted this move by jumping
6665         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6666         if(bookHit) {
6667                 static char bookMove[MSG_SIZ]; // a bit generous?
6668
6669                 strcpy(bookMove, "move ");
6670                 strcat(bookMove, bookHit);
6671                 message = bookMove;
6672                 cps = cps->other;
6673                 programStats.nodes = programStats.depth = programStats.time = 
6674                 programStats.score = programStats.got_only_move = 0;
6675                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6676
6677                 if(cps->lastPing != cps->lastPong) {
6678                     savedMessage = message; // args for deferred call
6679                     savedState = cps;
6680                     ScheduleDelayedEvent(DeferredBookMove, 10);
6681                     return;
6682                 }
6683                 goto FakeBookMove;
6684         }
6685
6686         return;
6687     }
6688
6689     /* Set special modes for chess engines.  Later something general
6690      *  could be added here; for now there is just one kludge feature,
6691      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6692      *  when "xboard" is given as an interactive command.
6693      */
6694     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6695         cps->useSigint = FALSE;
6696         cps->useSigterm = FALSE;
6697     }
6698     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6699       ParseFeatures(message+8, cps);
6700       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6701     }
6702
6703     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6704      * want this, I was asked to put it in, and obliged.
6705      */
6706     if (!strncmp(message, "setboard ", 9)) {
6707         Board initial_position;
6708
6709         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6710
6711         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6712             DisplayError(_("Bad FEN received from engine"), 0);
6713             return ;
6714         } else {
6715            Reset(TRUE, FALSE);
6716            CopyBoard(boards[0], initial_position);
6717            initialRulePlies = FENrulePlies;
6718            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6719            else gameMode = MachinePlaysBlack;                 
6720            DrawPosition(FALSE, boards[currentMove]);
6721         }
6722         return;
6723     }
6724
6725     /*
6726      * Look for communication commands
6727      */
6728     if (!strncmp(message, "telluser ", 9)) {
6729         DisplayNote(message + 9);
6730         return;
6731     }
6732     if (!strncmp(message, "tellusererror ", 14)) {
6733         cps->userError = 1;
6734         DisplayError(message + 14, 0);
6735         return;
6736     }
6737     if (!strncmp(message, "tellopponent ", 13)) {
6738       if (appData.icsActive) {
6739         if (loggedOn) {
6740           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6741           SendToICS(buf1);
6742         }
6743       } else {
6744         DisplayNote(message + 13);
6745       }
6746       return;
6747     }
6748     if (!strncmp(message, "tellothers ", 11)) {
6749       if (appData.icsActive) {
6750         if (loggedOn) {
6751           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6752           SendToICS(buf1);
6753         }
6754       }
6755       return;
6756     }
6757     if (!strncmp(message, "tellall ", 8)) {
6758       if (appData.icsActive) {
6759         if (loggedOn) {
6760           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6761           SendToICS(buf1);
6762         }
6763       } else {
6764         DisplayNote(message + 8);
6765       }
6766       return;
6767     }
6768     if (strncmp(message, "warning", 7) == 0) {
6769         /* Undocumented feature, use tellusererror in new code */
6770         DisplayError(message, 0);
6771         return;
6772     }
6773     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6774         strcpy(realname, cps->tidy);
6775         strcat(realname, " query");
6776         AskQuestion(realname, buf2, buf1, cps->pr);
6777         return;
6778     }
6779     /* Commands from the engine directly to ICS.  We don't allow these to be 
6780      *  sent until we are logged on. Crafty kibitzes have been known to 
6781      *  interfere with the login process.
6782      */
6783     if (loggedOn) {
6784         if (!strncmp(message, "tellics ", 8)) {
6785             SendToICS(message + 8);
6786             SendToICS("\n");
6787             return;
6788         }
6789         if (!strncmp(message, "tellicsnoalias ", 15)) {
6790             SendToICS(ics_prefix);
6791             SendToICS(message + 15);
6792             SendToICS("\n");
6793             return;
6794         }
6795         /* The following are for backward compatibility only */
6796         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6797             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6798             SendToICS(ics_prefix);
6799             SendToICS(message);
6800             SendToICS("\n");
6801             return;
6802         }
6803     }
6804     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6805         return;
6806     }
6807     /*
6808      * If the move is illegal, cancel it and redraw the board.
6809      * Also deal with other error cases.  Matching is rather loose
6810      * here to accommodate engines written before the spec.
6811      */
6812     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6813         strncmp(message, "Error", 5) == 0) {
6814         if (StrStr(message, "name") || 
6815             StrStr(message, "rating") || StrStr(message, "?") ||
6816             StrStr(message, "result") || StrStr(message, "board") ||
6817             StrStr(message, "bk") || StrStr(message, "computer") ||
6818             StrStr(message, "variant") || StrStr(message, "hint") ||
6819             StrStr(message, "random") || StrStr(message, "depth") ||
6820             StrStr(message, "accepted")) {
6821             return;
6822         }
6823         if (StrStr(message, "protover")) {
6824           /* Program is responding to input, so it's apparently done
6825              initializing, and this error message indicates it is
6826              protocol version 1.  So we don't need to wait any longer
6827              for it to initialize and send feature commands. */
6828           FeatureDone(cps, 1);
6829           cps->protocolVersion = 1;
6830           return;
6831         }
6832         cps->maybeThinking = FALSE;
6833
6834         if (StrStr(message, "draw")) {
6835             /* Program doesn't have "draw" command */
6836             cps->sendDrawOffers = 0;
6837             return;
6838         }
6839         if (cps->sendTime != 1 &&
6840             (StrStr(message, "time") || StrStr(message, "otim"))) {
6841           /* Program apparently doesn't have "time" or "otim" command */
6842           cps->sendTime = 0;
6843           return;
6844         }
6845         if (StrStr(message, "analyze")) {
6846             cps->analysisSupport = FALSE;
6847             cps->analyzing = FALSE;
6848             Reset(FALSE, TRUE);
6849             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6850             DisplayError(buf2, 0);
6851             return;
6852         }
6853         if (StrStr(message, "(no matching move)st")) {
6854           /* Special kludge for GNU Chess 4 only */
6855           cps->stKludge = TRUE;
6856           SendTimeControl(cps, movesPerSession, timeControl,
6857                           timeIncrement, appData.searchDepth,
6858                           searchTime);
6859           return;
6860         }
6861         if (StrStr(message, "(no matching move)sd")) {
6862           /* Special kludge for GNU Chess 4 only */
6863           cps->sdKludge = TRUE;
6864           SendTimeControl(cps, movesPerSession, timeControl,
6865                           timeIncrement, appData.searchDepth,
6866                           searchTime);
6867           return;
6868         }
6869         if (!StrStr(message, "llegal")) {
6870             return;
6871         }
6872         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6873             gameMode == IcsIdle) return;
6874         if (forwardMostMove <= backwardMostMove) return;
6875         if (pausing) PauseEvent();
6876       if(appData.forceIllegal) {
6877             // [HGM] illegal: machine refused move; force position after move into it
6878           SendToProgram("force\n", cps);
6879           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6880                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6881                 // when black is to move, while there might be nothing on a2 or black
6882                 // might already have the move. So send the board as if white has the move.
6883                 // But first we must change the stm of the engine, as it refused the last move
6884                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6885                 if(WhiteOnMove(forwardMostMove)) {
6886                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6887                     SendBoard(cps, forwardMostMove); // kludgeless board
6888                 } else {
6889                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6890                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6891                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6892                 }
6893           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6894             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6895                  gameMode == TwoMachinesPlay)
6896               SendToProgram("go\n", cps);
6897             return;
6898       } else
6899         if (gameMode == PlayFromGameFile) {
6900             /* Stop reading this game file */
6901             gameMode = EditGame;
6902             ModeHighlight();
6903         }
6904         currentMove = --forwardMostMove;
6905         DisplayMove(currentMove-1); /* before DisplayMoveError */
6906         SwitchClocks();
6907         DisplayBothClocks();
6908         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6909                 parseList[currentMove], cps->which);
6910         DisplayMoveError(buf1);
6911         DrawPosition(FALSE, boards[currentMove]);
6912
6913         /* [HGM] illegal-move claim should forfeit game when Xboard */
6914         /* only passes fully legal moves                            */
6915         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6916             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6917                                 "False illegal-move claim", GE_XBOARD );
6918         }
6919         return;
6920     }
6921     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6922         /* Program has a broken "time" command that
6923            outputs a string not ending in newline.
6924            Don't use it. */
6925         cps->sendTime = 0;
6926     }
6927     
6928     /*
6929      * If chess program startup fails, exit with an error message.
6930      * Attempts to recover here are futile.
6931      */
6932     if ((StrStr(message, "unknown host") != NULL)
6933         || (StrStr(message, "No remote directory") != NULL)
6934         || (StrStr(message, "not found") != NULL)
6935         || (StrStr(message, "No such file") != NULL)
6936         || (StrStr(message, "can't alloc") != NULL)
6937         || (StrStr(message, "Permission denied") != NULL)) {
6938
6939         cps->maybeThinking = FALSE;
6940         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6941                 cps->which, cps->program, cps->host, message);
6942         RemoveInputSource(cps->isr);
6943         DisplayFatalError(buf1, 0, 1);
6944         return;
6945     }
6946     
6947     /* 
6948      * Look for hint output
6949      */
6950     if (sscanf(message, "Hint: %s", buf1) == 1) {
6951         if (cps == &first && hintRequested) {
6952             hintRequested = FALSE;
6953             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6954                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6955                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6956                                     PosFlags(forwardMostMove),
6957                                     fromY, fromX, toY, toX, promoChar, buf1);
6958                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6959                 DisplayInformation(buf2);
6960             } else {
6961                 /* Hint move could not be parsed!? */
6962               snprintf(buf2, sizeof(buf2),
6963                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6964                         buf1, cps->which);
6965                 DisplayError(buf2, 0);
6966             }
6967         } else {
6968             strcpy(lastHint, buf1);
6969         }
6970         return;
6971     }
6972
6973     /*
6974      * Ignore other messages if game is not in progress
6975      */
6976     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6977         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6978
6979     /*
6980      * look for win, lose, draw, or draw offer
6981      */
6982     if (strncmp(message, "1-0", 3) == 0) {
6983         char *p, *q, *r = "";
6984         p = strchr(message, '{');
6985         if (p) {
6986             q = strchr(p, '}');
6987             if (q) {
6988                 *q = NULLCHAR;
6989                 r = p + 1;
6990             }
6991         }
6992         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6993         return;
6994     } else if (strncmp(message, "0-1", 3) == 0) {
6995         char *p, *q, *r = "";
6996         p = strchr(message, '{');
6997         if (p) {
6998             q = strchr(p, '}');
6999             if (q) {
7000                 *q = NULLCHAR;
7001                 r = p + 1;
7002             }
7003         }
7004         /* Kludge for Arasan 4.1 bug */
7005         if (strcmp(r, "Black resigns") == 0) {
7006             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7007             return;
7008         }
7009         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7010         return;
7011     } else if (strncmp(message, "1/2", 3) == 0) {
7012         char *p, *q, *r = "";
7013         p = strchr(message, '{');
7014         if (p) {
7015             q = strchr(p, '}');
7016             if (q) {
7017                 *q = NULLCHAR;
7018                 r = p + 1;
7019             }
7020         }
7021             
7022         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7023         return;
7024
7025     } else if (strncmp(message, "White resign", 12) == 0) {
7026         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7027         return;
7028     } else if (strncmp(message, "Black resign", 12) == 0) {
7029         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7030         return;
7031     } else if (strncmp(message, "White matches", 13) == 0 ||
7032                strncmp(message, "Black matches", 13) == 0   ) {
7033         /* [HGM] ignore GNUShogi noises */
7034         return;
7035     } else if (strncmp(message, "White", 5) == 0 &&
7036                message[5] != '(' &&
7037                StrStr(message, "Black") == NULL) {
7038         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7039         return;
7040     } else if (strncmp(message, "Black", 5) == 0 &&
7041                message[5] != '(') {
7042         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7043         return;
7044     } else if (strcmp(message, "resign") == 0 ||
7045                strcmp(message, "computer resigns") == 0) {
7046         switch (gameMode) {
7047           case MachinePlaysBlack:
7048           case IcsPlayingBlack:
7049             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7050             break;
7051           case MachinePlaysWhite:
7052           case IcsPlayingWhite:
7053             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7054             break;
7055           case TwoMachinesPlay:
7056             if (cps->twoMachinesColor[0] == 'w')
7057               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7058             else
7059               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7060             break;
7061           default:
7062             /* can't happen */
7063             break;
7064         }
7065         return;
7066     } else if (strncmp(message, "opponent mates", 14) == 0) {
7067         switch (gameMode) {
7068           case MachinePlaysBlack:
7069           case IcsPlayingBlack:
7070             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7071             break;
7072           case MachinePlaysWhite:
7073           case IcsPlayingWhite:
7074             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7075             break;
7076           case TwoMachinesPlay:
7077             if (cps->twoMachinesColor[0] == 'w')
7078               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7079             else
7080               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7081             break;
7082           default:
7083             /* can't happen */
7084             break;
7085         }
7086         return;
7087     } else if (strncmp(message, "computer mates", 14) == 0) {
7088         switch (gameMode) {
7089           case MachinePlaysBlack:
7090           case IcsPlayingBlack:
7091             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7092             break;
7093           case MachinePlaysWhite:
7094           case IcsPlayingWhite:
7095             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7096             break;
7097           case TwoMachinesPlay:
7098             if (cps->twoMachinesColor[0] == 'w')
7099               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7100             else
7101               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7102             break;
7103           default:
7104             /* can't happen */
7105             break;
7106         }
7107         return;
7108     } else if (strncmp(message, "checkmate", 9) == 0) {
7109         if (WhiteOnMove(forwardMostMove)) {
7110             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7111         } else {
7112             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7113         }
7114         return;
7115     } else if (strstr(message, "Draw") != NULL ||
7116                strstr(message, "game is a draw") != NULL) {
7117         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7118         return;
7119     } else if (strstr(message, "offer") != NULL &&
7120                strstr(message, "draw") != NULL) {
7121 #if ZIPPY
7122         if (appData.zippyPlay && first.initDone) {
7123             /* Relay offer to ICS */
7124             SendToICS(ics_prefix);
7125             SendToICS("draw\n");
7126         }
7127 #endif
7128         cps->offeredDraw = 2; /* valid until this engine moves twice */
7129         if (gameMode == TwoMachinesPlay) {
7130             if (cps->other->offeredDraw) {
7131                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7132             /* [HGM] in two-machine mode we delay relaying draw offer      */
7133             /* until after we also have move, to see if it is really claim */
7134             }
7135         } else if (gameMode == MachinePlaysWhite ||
7136                    gameMode == MachinePlaysBlack) {
7137           if (userOfferedDraw) {
7138             DisplayInformation(_("Machine accepts your draw offer"));
7139             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7140           } else {
7141             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7142           }
7143         }
7144     }
7145
7146     
7147     /*
7148      * Look for thinking output
7149      */
7150     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7151           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7152                                 ) {
7153         int plylev, mvleft, mvtot, curscore, time;
7154         char mvname[MOVE_LEN];
7155         u64 nodes; // [DM]
7156         char plyext;
7157         int ignore = FALSE;
7158         int prefixHint = FALSE;
7159         mvname[0] = NULLCHAR;
7160
7161         switch (gameMode) {
7162           case MachinePlaysBlack:
7163           case IcsPlayingBlack:
7164             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7165             break;
7166           case MachinePlaysWhite:
7167           case IcsPlayingWhite:
7168             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7169             break;
7170           case AnalyzeMode:
7171           case AnalyzeFile:
7172             break;
7173           case IcsObserving: /* [DM] icsEngineAnalyze */
7174             if (!appData.icsEngineAnalyze) ignore = TRUE;
7175             break;
7176           case TwoMachinesPlay:
7177             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7178                 ignore = TRUE;
7179             }
7180             break;
7181           default:
7182             ignore = TRUE;
7183             break;
7184         }
7185
7186         if (!ignore) {
7187             buf1[0] = NULLCHAR;
7188             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7189                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7190
7191                 if (plyext != ' ' && plyext != '\t') {
7192                     time *= 100;
7193                 }
7194
7195                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7196                 if( cps->scoreIsAbsolute && 
7197                     ( gameMode == MachinePlaysBlack ||
7198                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7199                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7200                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7201                      !WhiteOnMove(currentMove)
7202                     ) )
7203                 {
7204                     curscore = -curscore;
7205                 }
7206
7207
7208                 programStats.depth = plylev;
7209                 programStats.nodes = nodes;
7210                 programStats.time = time;
7211                 programStats.score = curscore;
7212                 programStats.got_only_move = 0;
7213
7214                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7215                         int ticklen;
7216
7217                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7218                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7219                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7220                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7221                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7222                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7223                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7224                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7225                 }
7226
7227                 /* Buffer overflow protection */
7228                 if (buf1[0] != NULLCHAR) {
7229                     if (strlen(buf1) >= sizeof(programStats.movelist)
7230                         && appData.debugMode) {
7231                         fprintf(debugFP,
7232                                 "PV is too long; using the first %u bytes.\n",
7233                                 (unsigned) sizeof(programStats.movelist) - 1);
7234                     }
7235
7236                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7237                 } else {
7238                     sprintf(programStats.movelist, " no PV\n");
7239                 }
7240
7241                 if (programStats.seen_stat) {
7242                     programStats.ok_to_send = 1;
7243                 }
7244
7245                 if (strchr(programStats.movelist, '(') != NULL) {
7246                     programStats.line_is_book = 1;
7247                     programStats.nr_moves = 0;
7248                     programStats.moves_left = 0;
7249                 } else {
7250                     programStats.line_is_book = 0;
7251                 }
7252
7253                 SendProgramStatsToFrontend( cps, &programStats );
7254
7255                 /* 
7256                     [AS] Protect the thinkOutput buffer from overflow... this
7257                     is only useful if buf1 hasn't overflowed first!
7258                 */
7259                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7260                         plylev, 
7261                         (gameMode == TwoMachinesPlay ?
7262                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7263                         ((double) curscore) / 100.0,
7264                         prefixHint ? lastHint : "",
7265                         prefixHint ? " " : "" );
7266
7267                 if( buf1[0] != NULLCHAR ) {
7268                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7269
7270                     if( strlen(buf1) > max_len ) {
7271                         if( appData.debugMode) {
7272                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7273                         }
7274                         buf1[max_len+1] = '\0';
7275                     }
7276
7277                     strcat( thinkOutput, buf1 );
7278                 }
7279
7280                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7281                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7282                     DisplayMove(currentMove - 1);
7283                 }
7284                 return;
7285
7286             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7287                 /* crafty (9.25+) says "(only move) <move>"
7288                  * if there is only 1 legal move
7289                  */
7290                 sscanf(p, "(only move) %s", buf1);
7291                 sprintf(thinkOutput, "%s (only move)", buf1);
7292                 sprintf(programStats.movelist, "%s (only move)", buf1);
7293                 programStats.depth = 1;
7294                 programStats.nr_moves = 1;
7295                 programStats.moves_left = 1;
7296                 programStats.nodes = 1;
7297                 programStats.time = 1;
7298                 programStats.got_only_move = 1;
7299
7300                 /* Not really, but we also use this member to
7301                    mean "line isn't going to change" (Crafty
7302                    isn't searching, so stats won't change) */
7303                 programStats.line_is_book = 1;
7304
7305                 SendProgramStatsToFrontend( cps, &programStats );
7306                 
7307                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7308                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7309                     DisplayMove(currentMove - 1);
7310                 }
7311                 return;
7312             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7313                               &time, &nodes, &plylev, &mvleft,
7314                               &mvtot, mvname) >= 5) {
7315                 /* The stat01: line is from Crafty (9.29+) in response
7316                    to the "." command */
7317                 programStats.seen_stat = 1;
7318                 cps->maybeThinking = TRUE;
7319
7320                 if (programStats.got_only_move || !appData.periodicUpdates)
7321                   return;
7322
7323                 programStats.depth = plylev;
7324                 programStats.time = time;
7325                 programStats.nodes = nodes;
7326                 programStats.moves_left = mvleft;
7327                 programStats.nr_moves = mvtot;
7328                 strcpy(programStats.move_name, mvname);
7329                 programStats.ok_to_send = 1;
7330                 programStats.movelist[0] = '\0';
7331
7332                 SendProgramStatsToFrontend( cps, &programStats );
7333
7334                 return;
7335
7336             } else if (strncmp(message,"++",2) == 0) {
7337                 /* Crafty 9.29+ outputs this */
7338                 programStats.got_fail = 2;
7339                 return;
7340
7341             } else if (strncmp(message,"--",2) == 0) {
7342                 /* Crafty 9.29+ outputs this */
7343                 programStats.got_fail = 1;
7344                 return;
7345
7346             } else if (thinkOutput[0] != NULLCHAR &&
7347                        strncmp(message, "    ", 4) == 0) {
7348                 unsigned message_len;
7349
7350                 p = message;
7351                 while (*p && *p == ' ') p++;
7352
7353                 message_len = strlen( p );
7354
7355                 /* [AS] Avoid buffer overflow */
7356                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7357                     strcat(thinkOutput, " ");
7358                     strcat(thinkOutput, p);
7359                 }
7360
7361                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7362                     strcat(programStats.movelist, " ");
7363                     strcat(programStats.movelist, p);
7364                 }
7365
7366                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7367                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7368                     DisplayMove(currentMove - 1);
7369                 }
7370                 return;
7371             }
7372         }
7373         else {
7374             buf1[0] = NULLCHAR;
7375
7376             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7377                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7378             {
7379                 ChessProgramStats cpstats;
7380
7381                 if (plyext != ' ' && plyext != '\t') {
7382                     time *= 100;
7383                 }
7384
7385                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7386                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7387                     curscore = -curscore;
7388                 }
7389
7390                 cpstats.depth = plylev;
7391                 cpstats.nodes = nodes;
7392                 cpstats.time = time;
7393                 cpstats.score = curscore;
7394                 cpstats.got_only_move = 0;
7395                 cpstats.movelist[0] = '\0';
7396
7397                 if (buf1[0] != NULLCHAR) {
7398                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7399                 }
7400
7401                 cpstats.ok_to_send = 0;
7402                 cpstats.line_is_book = 0;
7403                 cpstats.nr_moves = 0;
7404                 cpstats.moves_left = 0;
7405
7406                 SendProgramStatsToFrontend( cps, &cpstats );
7407             }
7408         }
7409     }
7410 }
7411
7412
7413 /* Parse a game score from the character string "game", and
7414    record it as the history of the current game.  The game
7415    score is NOT assumed to start from the standard position. 
7416    The display is not updated in any way.
7417    */
7418 void
7419 ParseGameHistory(game)
7420      char *game;
7421 {
7422     ChessMove moveType;
7423     int fromX, fromY, toX, toY, boardIndex;
7424     char promoChar;
7425     char *p, *q;
7426     char buf[MSG_SIZ];
7427
7428     if (appData.debugMode)
7429       fprintf(debugFP, "Parsing game history: %s\n", game);
7430
7431     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7432     gameInfo.site = StrSave(appData.icsHost);
7433     gameInfo.date = PGNDate();
7434     gameInfo.round = StrSave("-");
7435
7436     /* Parse out names of players */
7437     while (*game == ' ') game++;
7438     p = buf;
7439     while (*game != ' ') *p++ = *game++;
7440     *p = NULLCHAR;
7441     gameInfo.white = StrSave(buf);
7442     while (*game == ' ') game++;
7443     p = buf;
7444     while (*game != ' ' && *game != '\n') *p++ = *game++;
7445     *p = NULLCHAR;
7446     gameInfo.black = StrSave(buf);
7447
7448     /* Parse moves */
7449     boardIndex = blackPlaysFirst ? 1 : 0;
7450     yynewstr(game);
7451     for (;;) {
7452         yyboardindex = boardIndex;
7453         moveType = (ChessMove) yylex();
7454         switch (moveType) {
7455           case IllegalMove:             /* maybe suicide chess, etc. */
7456   if (appData.debugMode) {
7457     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7458     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7459     setbuf(debugFP, NULL);
7460   }
7461           case WhitePromotionChancellor:
7462           case BlackPromotionChancellor:
7463           case WhitePromotionArchbishop:
7464           case BlackPromotionArchbishop:
7465           case WhitePromotionQueen:
7466           case BlackPromotionQueen:
7467           case WhitePromotionRook:
7468           case BlackPromotionRook:
7469           case WhitePromotionBishop:
7470           case BlackPromotionBishop:
7471           case WhitePromotionKnight:
7472           case BlackPromotionKnight:
7473           case WhitePromotionKing:
7474           case BlackPromotionKing:
7475           case NormalMove:
7476           case WhiteCapturesEnPassant:
7477           case BlackCapturesEnPassant:
7478           case WhiteKingSideCastle:
7479           case WhiteQueenSideCastle:
7480           case BlackKingSideCastle:
7481           case BlackQueenSideCastle:
7482           case WhiteKingSideCastleWild:
7483           case WhiteQueenSideCastleWild:
7484           case BlackKingSideCastleWild:
7485           case BlackQueenSideCastleWild:
7486           /* PUSH Fabien */
7487           case WhiteHSideCastleFR:
7488           case WhiteASideCastleFR:
7489           case BlackHSideCastleFR:
7490           case BlackASideCastleFR:
7491           /* POP Fabien */
7492             fromX = currentMoveString[0] - AAA;
7493             fromY = currentMoveString[1] - ONE;
7494             toX = currentMoveString[2] - AAA;
7495             toY = currentMoveString[3] - ONE;
7496             promoChar = currentMoveString[4];
7497             break;
7498           case WhiteDrop:
7499           case BlackDrop:
7500             fromX = moveType == WhiteDrop ?
7501               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7502             (int) CharToPiece(ToLower(currentMoveString[0]));
7503             fromY = DROP_RANK;
7504             toX = currentMoveString[2] - AAA;
7505             toY = currentMoveString[3] - ONE;
7506             promoChar = NULLCHAR;
7507             break;
7508           case AmbiguousMove:
7509             /* bug? */
7510             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7511   if (appData.debugMode) {
7512     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7513     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7514     setbuf(debugFP, NULL);
7515   }
7516             DisplayError(buf, 0);
7517             return;
7518           case ImpossibleMove:
7519             /* bug? */
7520             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7521   if (appData.debugMode) {
7522     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7523     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7524     setbuf(debugFP, NULL);
7525   }
7526             DisplayError(buf, 0);
7527             return;
7528           case (ChessMove) 0:   /* end of file */
7529             if (boardIndex < backwardMostMove) {
7530                 /* Oops, gap.  How did that happen? */
7531                 DisplayError(_("Gap in move list"), 0);
7532                 return;
7533             }
7534             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7535             if (boardIndex > forwardMostMove) {
7536                 forwardMostMove = boardIndex;
7537             }
7538             return;
7539           case ElapsedTime:
7540             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7541                 strcat(parseList[boardIndex-1], " ");
7542                 strcat(parseList[boardIndex-1], yy_text);
7543             }
7544             continue;
7545           case Comment:
7546           case PGNTag:
7547           case NAG:
7548           default:
7549             /* ignore */
7550             continue;
7551           case WhiteWins:
7552           case BlackWins:
7553           case GameIsDrawn:
7554           case GameUnfinished:
7555             if (gameMode == IcsExamining) {
7556                 if (boardIndex < backwardMostMove) {
7557                     /* Oops, gap.  How did that happen? */
7558                     return;
7559                 }
7560                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7561                 return;
7562             }
7563             gameInfo.result = moveType;
7564             p = strchr(yy_text, '{');
7565             if (p == NULL) p = strchr(yy_text, '(');
7566             if (p == NULL) {
7567                 p = yy_text;
7568                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7569             } else {
7570                 q = strchr(p, *p == '{' ? '}' : ')');
7571                 if (q != NULL) *q = NULLCHAR;
7572                 p++;
7573             }
7574             gameInfo.resultDetails = StrSave(p);
7575             continue;
7576         }
7577         if (boardIndex >= forwardMostMove &&
7578             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7579             backwardMostMove = blackPlaysFirst ? 1 : 0;
7580             return;
7581         }
7582         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7583                                  fromY, fromX, toY, toX, promoChar,
7584                                  parseList[boardIndex]);
7585         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7586         /* currentMoveString is set as a side-effect of yylex */
7587         strcpy(moveList[boardIndex], currentMoveString);
7588         strcat(moveList[boardIndex], "\n");
7589         boardIndex++;
7590         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7591         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7592           case MT_NONE:
7593           case MT_STALEMATE:
7594           default:
7595             break;
7596           case MT_CHECK:
7597             if(gameInfo.variant != VariantShogi)
7598                 strcat(parseList[boardIndex - 1], "+");
7599             break;
7600           case MT_CHECKMATE:
7601           case MT_STAINMATE:
7602             strcat(parseList[boardIndex - 1], "#");
7603             break;
7604         }
7605     }
7606 }
7607
7608
7609 /* Apply a move to the given board  */
7610 void
7611 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7612      int fromX, fromY, toX, toY;
7613      int promoChar;
7614      Board board;
7615 {
7616   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7617   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7618
7619     /* [HGM] compute & store e.p. status and castling rights for new position */
7620     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7621     { int i;
7622
7623       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7624       oldEP = (signed char)board[EP_STATUS];
7625       board[EP_STATUS] = EP_NONE;
7626
7627       if( board[toY][toX] != EmptySquare ) 
7628            board[EP_STATUS] = EP_CAPTURE;  
7629
7630       if( board[fromY][fromX] == WhitePawn ) {
7631            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7632                board[EP_STATUS] = EP_PAWN_MOVE;
7633            if( toY-fromY==2) {
7634                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7635                         gameInfo.variant != VariantBerolina || toX < fromX)
7636                       board[EP_STATUS] = toX | berolina;
7637                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7638                         gameInfo.variant != VariantBerolina || toX > fromX) 
7639                       board[EP_STATUS] = toX;
7640            }
7641       } else 
7642       if( board[fromY][fromX] == BlackPawn ) {
7643            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7644                board[EP_STATUS] = EP_PAWN_MOVE; 
7645            if( toY-fromY== -2) {
7646                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7647                         gameInfo.variant != VariantBerolina || toX < fromX)
7648                       board[EP_STATUS] = toX | berolina;
7649                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7650                         gameInfo.variant != VariantBerolina || toX > fromX) 
7651                       board[EP_STATUS] = toX;
7652            }
7653        }
7654
7655        for(i=0; i<nrCastlingRights; i++) {
7656            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7657               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7658              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7659        }
7660
7661     }
7662
7663   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7664   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7665        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7666          
7667   if (fromX == toX && fromY == toY) return;
7668
7669   if (fromY == DROP_RANK) {
7670         /* must be first */
7671         piece = board[toY][toX] = (ChessSquare) fromX;
7672   } else {
7673      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7674      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7675      if(gameInfo.variant == VariantKnightmate)
7676          king += (int) WhiteUnicorn - (int) WhiteKing;
7677
7678     /* Code added by Tord: */
7679     /* FRC castling assumed when king captures friendly rook. */
7680     if (board[fromY][fromX] == WhiteKing &&
7681              board[toY][toX] == WhiteRook) {
7682       board[fromY][fromX] = EmptySquare;
7683       board[toY][toX] = EmptySquare;
7684       if(toX > fromX) {
7685         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7686       } else {
7687         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7688       }
7689     } else if (board[fromY][fromX] == BlackKing &&
7690                board[toY][toX] == BlackRook) {
7691       board[fromY][fromX] = EmptySquare;
7692       board[toY][toX] = EmptySquare;
7693       if(toX > fromX) {
7694         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7695       } else {
7696         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7697       }
7698     /* End of code added by Tord */
7699
7700     } else if (board[fromY][fromX] == king
7701         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7702         && toY == fromY && toX > fromX+1) {
7703         board[fromY][fromX] = EmptySquare;
7704         board[toY][toX] = king;
7705         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7706         board[fromY][BOARD_RGHT-1] = EmptySquare;
7707     } else if (board[fromY][fromX] == king
7708         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7709                && toY == fromY && toX < fromX-1) {
7710         board[fromY][fromX] = EmptySquare;
7711         board[toY][toX] = king;
7712         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7713         board[fromY][BOARD_LEFT] = EmptySquare;
7714     } else if (board[fromY][fromX] == WhitePawn
7715                && toY >= BOARD_HEIGHT-promoRank
7716                && gameInfo.variant != VariantXiangqi
7717                ) {
7718         /* white pawn promotion */
7719         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7720         if (board[toY][toX] == EmptySquare) {
7721             board[toY][toX] = WhiteQueen;
7722         }
7723         if(gameInfo.variant==VariantBughouse ||
7724            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7725             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7726         board[fromY][fromX] = EmptySquare;
7727     } else if ((fromY == BOARD_HEIGHT-4)
7728                && (toX != fromX)
7729                && gameInfo.variant != VariantXiangqi
7730                && gameInfo.variant != VariantBerolina
7731                && (board[fromY][fromX] == WhitePawn)
7732                && (board[toY][toX] == EmptySquare)) {
7733         board[fromY][fromX] = EmptySquare;
7734         board[toY][toX] = WhitePawn;
7735         captured = board[toY - 1][toX];
7736         board[toY - 1][toX] = EmptySquare;
7737     } else if ((fromY == BOARD_HEIGHT-4)
7738                && (toX == fromX)
7739                && gameInfo.variant == VariantBerolina
7740                && (board[fromY][fromX] == WhitePawn)
7741                && (board[toY][toX] == EmptySquare)) {
7742         board[fromY][fromX] = EmptySquare;
7743         board[toY][toX] = WhitePawn;
7744         if(oldEP & EP_BEROLIN_A) {
7745                 captured = board[fromY][fromX-1];
7746                 board[fromY][fromX-1] = EmptySquare;
7747         }else{  captured = board[fromY][fromX+1];
7748                 board[fromY][fromX+1] = EmptySquare;
7749         }
7750     } else if (board[fromY][fromX] == king
7751         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7752                && toY == fromY && toX > fromX+1) {
7753         board[fromY][fromX] = EmptySquare;
7754         board[toY][toX] = king;
7755         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7756         board[fromY][BOARD_RGHT-1] = EmptySquare;
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_LEFT];
7763         board[fromY][BOARD_LEFT] = EmptySquare;
7764     } else if (fromY == 7 && fromX == 3
7765                && board[fromY][fromX] == BlackKing
7766                && toY == 7 && toX == 5) {
7767         board[fromY][fromX] = EmptySquare;
7768         board[toY][toX] = BlackKing;
7769         board[fromY][7] = EmptySquare;
7770         board[toY][4] = BlackRook;
7771     } else if (fromY == 7 && fromX == 3
7772                && board[fromY][fromX] == BlackKing
7773                && toY == 7 && toX == 1) {
7774         board[fromY][fromX] = EmptySquare;
7775         board[toY][toX] = BlackKing;
7776         board[fromY][0] = EmptySquare;
7777         board[toY][2] = BlackRook;
7778     } else if (board[fromY][fromX] == BlackPawn
7779                && toY < promoRank
7780                && gameInfo.variant != VariantXiangqi
7781                ) {
7782         /* black pawn promotion */
7783         board[toY][toX] = CharToPiece(ToLower(promoChar));
7784         if (board[toY][toX] == EmptySquare) {
7785             board[toY][toX] = BlackQueen;
7786         }
7787         if(gameInfo.variant==VariantBughouse ||
7788            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7789             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7790         board[fromY][fromX] = EmptySquare;
7791     } else if ((fromY == 3)
7792                && (toX != fromX)
7793                && gameInfo.variant != VariantXiangqi
7794                && gameInfo.variant != VariantBerolina
7795                && (board[fromY][fromX] == BlackPawn)
7796                && (board[toY][toX] == EmptySquare)) {
7797         board[fromY][fromX] = EmptySquare;
7798         board[toY][toX] = BlackPawn;
7799         captured = board[toY + 1][toX];
7800         board[toY + 1][toX] = EmptySquare;
7801     } else if ((fromY == 3)
7802                && (toX == fromX)
7803                && gameInfo.variant == VariantBerolina
7804                && (board[fromY][fromX] == BlackPawn)
7805                && (board[toY][toX] == EmptySquare)) {
7806         board[fromY][fromX] = EmptySquare;
7807         board[toY][toX] = BlackPawn;
7808         if(oldEP & EP_BEROLIN_A) {
7809                 captured = board[fromY][fromX-1];
7810                 board[fromY][fromX-1] = EmptySquare;
7811         }else{  captured = board[fromY][fromX+1];
7812                 board[fromY][fromX+1] = EmptySquare;
7813         }
7814     } else {
7815         board[toY][toX] = board[fromY][fromX];
7816         board[fromY][fromX] = EmptySquare;
7817     }
7818
7819     /* [HGM] now we promote for Shogi, if needed */
7820     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7821         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7822   }
7823
7824     if (gameInfo.holdingsWidth != 0) {
7825
7826       /* !!A lot more code needs to be written to support holdings  */
7827       /* [HGM] OK, so I have written it. Holdings are stored in the */
7828       /* penultimate board files, so they are automaticlly stored   */
7829       /* in the game history.                                       */
7830       if (fromY == DROP_RANK) {
7831         /* Delete from holdings, by decreasing count */
7832         /* and erasing image if necessary            */
7833         p = (int) fromX;
7834         if(p < (int) BlackPawn) { /* white drop */
7835              p -= (int)WhitePawn;
7836                  p = PieceToNumber((ChessSquare)p);
7837              if(p >= gameInfo.holdingsSize) p = 0;
7838              if(--board[p][BOARD_WIDTH-2] <= 0)
7839                   board[p][BOARD_WIDTH-1] = EmptySquare;
7840              if((int)board[p][BOARD_WIDTH-2] < 0)
7841                         board[p][BOARD_WIDTH-2] = 0;
7842         } else {                  /* black drop */
7843              p -= (int)BlackPawn;
7844                  p = PieceToNumber((ChessSquare)p);
7845              if(p >= gameInfo.holdingsSize) p = 0;
7846              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7847                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7848              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7849                         board[BOARD_HEIGHT-1-p][1] = 0;
7850         }
7851       }
7852       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7853           && gameInfo.variant != VariantBughouse        ) {
7854         /* [HGM] holdings: Add to holdings, if holdings exist */
7855         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7856                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7857                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7858         }
7859         p = (int) captured;
7860         if (p >= (int) BlackPawn) {
7861           p -= (int)BlackPawn;
7862           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7863                   /* in Shogi restore piece to its original  first */
7864                   captured = (ChessSquare) (DEMOTED captured);
7865                   p = DEMOTED p;
7866           }
7867           p = PieceToNumber((ChessSquare)p);
7868           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7869           board[p][BOARD_WIDTH-2]++;
7870           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7871         } else {
7872           p -= (int)WhitePawn;
7873           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7874                   captured = (ChessSquare) (DEMOTED captured);
7875                   p = DEMOTED p;
7876           }
7877           p = PieceToNumber((ChessSquare)p);
7878           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7879           board[BOARD_HEIGHT-1-p][1]++;
7880           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7881         }
7882       }
7883     } else if (gameInfo.variant == VariantAtomic) {
7884       if (captured != EmptySquare) {
7885         int y, x;
7886         for (y = toY-1; y <= toY+1; y++) {
7887           for (x = toX-1; x <= toX+1; x++) {
7888             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7889                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7890               board[y][x] = EmptySquare;
7891             }
7892           }
7893         }
7894         board[toY][toX] = EmptySquare;
7895       }
7896     }
7897     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7898         /* [HGM] Shogi promotions */
7899         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7900     }
7901
7902     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7903                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7904         // [HGM] superchess: take promotion piece out of holdings
7905         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7906         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7907             if(!--board[k][BOARD_WIDTH-2])
7908                 board[k][BOARD_WIDTH-1] = EmptySquare;
7909         } else {
7910             if(!--board[BOARD_HEIGHT-1-k][1])
7911                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7912         }
7913     }
7914
7915 }
7916
7917 /* Updates forwardMostMove */
7918 void
7919 MakeMove(fromX, fromY, toX, toY, promoChar)
7920      int fromX, fromY, toX, toY;
7921      int promoChar;
7922 {
7923 //    forwardMostMove++; // [HGM] bare: moved downstream
7924
7925     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7926         int timeLeft; static int lastLoadFlag=0; int king, piece;
7927         piece = boards[forwardMostMove][fromY][fromX];
7928         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7929         if(gameInfo.variant == VariantKnightmate)
7930             king += (int) WhiteUnicorn - (int) WhiteKing;
7931         if(forwardMostMove == 0) {
7932             if(blackPlaysFirst) 
7933                 fprintf(serverMoves, "%s;", second.tidy);
7934             fprintf(serverMoves, "%s;", first.tidy);
7935             if(!blackPlaysFirst) 
7936                 fprintf(serverMoves, "%s;", second.tidy);
7937         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7938         lastLoadFlag = loadFlag;
7939         // print base move
7940         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7941         // print castling suffix
7942         if( toY == fromY && piece == king ) {
7943             if(toX-fromX > 1)
7944                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7945             if(fromX-toX >1)
7946                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7947         }
7948         // e.p. suffix
7949         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7950              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7951              boards[forwardMostMove][toY][toX] == EmptySquare
7952              && fromX != toX )
7953                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7954         // promotion suffix
7955         if(promoChar != NULLCHAR)
7956                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7957         if(!loadFlag) {
7958             fprintf(serverMoves, "/%d/%d",
7959                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7960             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7961             else                      timeLeft = blackTimeRemaining/1000;
7962             fprintf(serverMoves, "/%d", timeLeft);
7963         }
7964         fflush(serverMoves);
7965     }
7966
7967     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7968       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7969                         0, 1);
7970       return;
7971     }
7972     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7973     if (commentList[forwardMostMove+1] != NULL) {
7974         free(commentList[forwardMostMove+1]);
7975         commentList[forwardMostMove+1] = NULL;
7976     }
7977     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7978     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7979     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7980     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7981     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7982     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7983     gameInfo.result = GameUnfinished;
7984     if (gameInfo.resultDetails != NULL) {
7985         free(gameInfo.resultDetails);
7986         gameInfo.resultDetails = NULL;
7987     }
7988     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7989                               moveList[forwardMostMove - 1]);
7990     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7991                              PosFlags(forwardMostMove - 1),
7992                              fromY, fromX, toY, toX, promoChar,
7993                              parseList[forwardMostMove - 1]);
7994     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7995       case MT_NONE:
7996       case MT_STALEMATE:
7997       default:
7998         break;
7999       case MT_CHECK:
8000         if(gameInfo.variant != VariantShogi)
8001             strcat(parseList[forwardMostMove - 1], "+");
8002         break;
8003       case MT_CHECKMATE:
8004       case MT_STAINMATE:
8005         strcat(parseList[forwardMostMove - 1], "#");
8006         break;
8007     }
8008     if (appData.debugMode) {
8009         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8010     }
8011
8012 }
8013
8014 /* Updates currentMove if not pausing */
8015 void
8016 ShowMove(fromX, fromY, toX, toY)
8017 {
8018     int instant = (gameMode == PlayFromGameFile) ?
8019         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8020     if(appData.noGUI) return;
8021     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8022         if (!instant) {
8023             if (forwardMostMove == currentMove + 1) {
8024                 AnimateMove(boards[forwardMostMove - 1],
8025                             fromX, fromY, toX, toY);
8026             }
8027             if (appData.highlightLastMove) {
8028                 SetHighlights(fromX, fromY, toX, toY);
8029             }
8030         }
8031         currentMove = forwardMostMove;
8032     }
8033
8034     if (instant) return;
8035
8036     DisplayMove(currentMove - 1);
8037     DrawPosition(FALSE, boards[currentMove]);
8038     DisplayBothClocks();
8039     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8040 }
8041
8042 void SendEgtPath(ChessProgramState *cps)
8043 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8044         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8045
8046         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8047
8048         while(*p) {
8049             char c, *q = name+1, *r, *s;
8050
8051             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8052             while(*p && *p != ',') *q++ = *p++;
8053             *q++ = ':'; *q = 0;
8054             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8055                 strcmp(name, ",nalimov:") == 0 ) {
8056                 // take nalimov path from the menu-changeable option first, if it is defined
8057                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8058                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8059             } else
8060             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8061                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8062                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8063                 s = r = StrStr(s, ":") + 1; // beginning of path info
8064                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8065                 c = *r; *r = 0;             // temporarily null-terminate path info
8066                     *--q = 0;               // strip of trailig ':' from name
8067                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8068                 *r = c;
8069                 SendToProgram(buf,cps);     // send egtbpath command for this format
8070             }
8071             if(*p == ',') p++; // read away comma to position for next format name
8072         }
8073 }
8074
8075 void
8076 InitChessProgram(cps, setup)
8077      ChessProgramState *cps;
8078      int setup; /* [HGM] needed to setup FRC opening position */
8079 {
8080     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8081     if (appData.noChessProgram) return;
8082     hintRequested = FALSE;
8083     bookRequested = FALSE;
8084
8085     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8086     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8087     if(cps->memSize) { /* [HGM] memory */
8088         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8089         SendToProgram(buf, cps);
8090     }
8091     SendEgtPath(cps); /* [HGM] EGT */
8092     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8093         sprintf(buf, "cores %d\n", appData.smpCores);
8094         SendToProgram(buf, cps);
8095     }
8096
8097     SendToProgram(cps->initString, cps);
8098     if (gameInfo.variant != VariantNormal &&
8099         gameInfo.variant != VariantLoadable
8100         /* [HGM] also send variant if board size non-standard */
8101         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8102                                             ) {
8103       char *v = VariantName(gameInfo.variant);
8104       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8105         /* [HGM] in protocol 1 we have to assume all variants valid */
8106         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8107         DisplayFatalError(buf, 0, 1);
8108         return;
8109       }
8110
8111       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8112       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8113       if( gameInfo.variant == VariantXiangqi )
8114            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8115       if( gameInfo.variant == VariantShogi )
8116            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8117       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8118            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8119       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8120                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8121            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8122       if( gameInfo.variant == VariantCourier )
8123            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8124       if( gameInfo.variant == VariantSuper )
8125            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8126       if( gameInfo.variant == VariantGreat )
8127            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8128
8129       if(overruled) {
8130            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8131                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8132            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8133            if(StrStr(cps->variants, b) == NULL) { 
8134                // specific sized variant not known, check if general sizing allowed
8135                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8136                    if(StrStr(cps->variants, "boardsize") == NULL) {
8137                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8138                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8139                        DisplayFatalError(buf, 0, 1);
8140                        return;
8141                    }
8142                    /* [HGM] here we really should compare with the maximum supported board size */
8143                }
8144            }
8145       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8146       sprintf(buf, "variant %s\n", b);
8147       SendToProgram(buf, cps);
8148     }
8149     currentlyInitializedVariant = gameInfo.variant;
8150
8151     /* [HGM] send opening position in FRC to first engine */
8152     if(setup) {
8153           SendToProgram("force\n", cps);
8154           SendBoard(cps, 0);
8155           /* engine is now in force mode! Set flag to wake it up after first move. */
8156           setboardSpoiledMachineBlack = 1;
8157     }
8158
8159     if (cps->sendICS) {
8160       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8161       SendToProgram(buf, cps);
8162     }
8163     cps->maybeThinking = FALSE;
8164     cps->offeredDraw = 0;
8165     if (!appData.icsActive) {
8166         SendTimeControl(cps, movesPerSession, timeControl,
8167                         timeIncrement, appData.searchDepth,
8168                         searchTime);
8169     }
8170     if (appData.showThinking 
8171         // [HGM] thinking: four options require thinking output to be sent
8172         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8173                                 ) {
8174         SendToProgram("post\n", cps);
8175     }
8176     SendToProgram("hard\n", cps);
8177     if (!appData.ponderNextMove) {
8178         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8179            it without being sure what state we are in first.  "hard"
8180            is not a toggle, so that one is OK.
8181          */
8182         SendToProgram("easy\n", cps);
8183     }
8184     if (cps->usePing) {
8185       sprintf(buf, "ping %d\n", ++cps->lastPing);
8186       SendToProgram(buf, cps);
8187     }
8188     cps->initDone = TRUE;
8189 }   
8190
8191
8192 void
8193 StartChessProgram(cps)
8194      ChessProgramState *cps;
8195 {
8196     char buf[MSG_SIZ];
8197     int err;
8198
8199     if (appData.noChessProgram) return;
8200     cps->initDone = FALSE;
8201
8202     if (strcmp(cps->host, "localhost") == 0) {
8203         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8204     } else if (*appData.remoteShell == NULLCHAR) {
8205         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8206     } else {
8207         if (*appData.remoteUser == NULLCHAR) {
8208           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8209                     cps->program);
8210         } else {
8211           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8212                     cps->host, appData.remoteUser, cps->program);
8213         }
8214         err = StartChildProcess(buf, "", &cps->pr);
8215     }
8216     
8217     if (err != 0) {
8218         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8219         DisplayFatalError(buf, err, 1);
8220         cps->pr = NoProc;
8221         cps->isr = NULL;
8222         return;
8223     }
8224     
8225     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8226     if (cps->protocolVersion > 1) {
8227       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8228       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8229       cps->comboCnt = 0;  //                and values of combo boxes
8230       SendToProgram(buf, cps);
8231     } else {
8232       SendToProgram("xboard\n", cps);
8233     }
8234 }
8235
8236
8237 void
8238 TwoMachinesEventIfReady P((void))
8239 {
8240   if (first.lastPing != first.lastPong) {
8241     DisplayMessage("", _("Waiting for first chess program"));
8242     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8243     return;
8244   }
8245   if (second.lastPing != second.lastPong) {
8246     DisplayMessage("", _("Waiting for second chess program"));
8247     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8248     return;
8249   }
8250   ThawUI();
8251   TwoMachinesEvent();
8252 }
8253
8254 void
8255 NextMatchGame P((void))
8256 {
8257     int index; /* [HGM] autoinc: step load index during match */
8258     Reset(FALSE, TRUE);
8259     if (*appData.loadGameFile != NULLCHAR) {
8260         index = appData.loadGameIndex;
8261         if(index < 0) { // [HGM] autoinc
8262             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8263             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8264         } 
8265         LoadGameFromFile(appData.loadGameFile,
8266                          index,
8267                          appData.loadGameFile, FALSE);
8268     } else if (*appData.loadPositionFile != NULLCHAR) {
8269         index = appData.loadPositionIndex;
8270         if(index < 0) { // [HGM] autoinc
8271             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8272             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8273         } 
8274         LoadPositionFromFile(appData.loadPositionFile,
8275                              index,
8276                              appData.loadPositionFile);
8277     }
8278     TwoMachinesEventIfReady();
8279 }
8280
8281 void UserAdjudicationEvent( int result )
8282 {
8283     ChessMove gameResult = GameIsDrawn;
8284
8285     if( result > 0 ) {
8286         gameResult = WhiteWins;
8287     }
8288     else if( result < 0 ) {
8289         gameResult = BlackWins;
8290     }
8291
8292     if( gameMode == TwoMachinesPlay ) {
8293         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8294     }
8295 }
8296
8297
8298 // [HGM] save: calculate checksum of game to make games easily identifiable
8299 int StringCheckSum(char *s)
8300 {
8301         int i = 0;
8302         if(s==NULL) return 0;
8303         while(*s) i = i*259 + *s++;
8304         return i;
8305 }
8306
8307 int GameCheckSum()
8308 {
8309         int i, sum=0;
8310         for(i=backwardMostMove; i<forwardMostMove; i++) {
8311                 sum += pvInfoList[i].depth;
8312                 sum += StringCheckSum(parseList[i]);
8313                 sum += StringCheckSum(commentList[i]);
8314                 sum *= 261;
8315         }
8316         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8317         return sum + StringCheckSum(commentList[i]);
8318 } // end of save patch
8319
8320 void
8321 GameEnds(result, resultDetails, whosays)
8322      ChessMove result;
8323      char *resultDetails;
8324      int whosays;
8325 {
8326     GameMode nextGameMode;
8327     int isIcsGame;
8328     char buf[MSG_SIZ];
8329
8330     if(endingGame) return; /* [HGM] crash: forbid recursion */
8331     endingGame = 1;
8332
8333     if (appData.debugMode) {
8334       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8335               result, resultDetails ? resultDetails : "(null)", whosays);
8336     }
8337
8338     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8339         /* If we are playing on ICS, the server decides when the
8340            game is over, but the engine can offer to draw, claim 
8341            a draw, or resign. 
8342          */
8343 #if ZIPPY
8344         if (appData.zippyPlay && first.initDone) {
8345             if (result == GameIsDrawn) {
8346                 /* In case draw still needs to be claimed */
8347                 SendToICS(ics_prefix);
8348                 SendToICS("draw\n");
8349             } else if (StrCaseStr(resultDetails, "resign")) {
8350                 SendToICS(ics_prefix);
8351                 SendToICS("resign\n");
8352             }
8353         }
8354 #endif
8355         endingGame = 0; /* [HGM] crash */
8356         return;
8357     }
8358
8359     /* If we're loading the game from a file, stop */
8360     if (whosays == GE_FILE) {
8361       (void) StopLoadGameTimer();
8362       gameFileFP = NULL;
8363     }
8364
8365     /* Cancel draw offers */
8366     first.offeredDraw = second.offeredDraw = 0;
8367
8368     /* If this is an ICS game, only ICS can really say it's done;
8369        if not, anyone can. */
8370     isIcsGame = (gameMode == IcsPlayingWhite || 
8371                  gameMode == IcsPlayingBlack || 
8372                  gameMode == IcsObserving    || 
8373                  gameMode == IcsExamining);
8374
8375     if (!isIcsGame || whosays == GE_ICS) {
8376         /* OK -- not an ICS game, or ICS said it was done */
8377         StopClocks();
8378         if (!isIcsGame && !appData.noChessProgram) 
8379           SetUserThinkingEnables();
8380     
8381         /* [HGM] if a machine claims the game end we verify this claim */
8382         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8383             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8384                 char claimer;
8385                 ChessMove trueResult = (ChessMove) -1;
8386
8387                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8388                                             first.twoMachinesColor[0] :
8389                                             second.twoMachinesColor[0] ;
8390
8391                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8392                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8393                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8394                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8395                 } else
8396                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8397                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8398                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8399                 } else
8400                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8401                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8402                 }
8403
8404                 // now verify win claims, but not in drop games, as we don't understand those yet
8405                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8406                                                  || gameInfo.variant == VariantGreat) &&
8407                     (result == WhiteWins && claimer == 'w' ||
8408                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8409                       if (appData.debugMode) {
8410                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8411                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8412                       }
8413                       if(result != trueResult) {
8414                               sprintf(buf, "False win claim: '%s'", resultDetails);
8415                               result = claimer == 'w' ? BlackWins : WhiteWins;
8416                               resultDetails = buf;
8417                       }
8418                 } else
8419                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8420                     && (forwardMostMove <= backwardMostMove ||
8421                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8422                         (claimer=='b')==(forwardMostMove&1))
8423                                                                                   ) {
8424                       /* [HGM] verify: draws that were not flagged are false claims */
8425                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8426                       result = claimer == 'w' ? BlackWins : WhiteWins;
8427                       resultDetails = buf;
8428                 }
8429                 /* (Claiming a loss is accepted no questions asked!) */
8430             }
8431             /* [HGM] bare: don't allow bare King to win */
8432             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8433                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8434                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8435                && result != GameIsDrawn)
8436             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8437                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8438                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8439                         if(p >= 0 && p <= (int)WhiteKing) k++;
8440                 }
8441                 if (appData.debugMode) {
8442                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8443                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8444                 }
8445                 if(k <= 1) {
8446                         result = GameIsDrawn;
8447                         sprintf(buf, "%s but bare king", resultDetails);
8448                         resultDetails = buf;
8449                 }
8450             }
8451         }
8452
8453
8454         if(serverMoves != NULL && !loadFlag) { char c = '=';
8455             if(result==WhiteWins) c = '+';
8456             if(result==BlackWins) c = '-';
8457             if(resultDetails != NULL)
8458                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8459         }
8460         if (resultDetails != NULL) {
8461             gameInfo.result = result;
8462             gameInfo.resultDetails = StrSave(resultDetails);
8463
8464             /* display last move only if game was not loaded from file */
8465             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8466                 DisplayMove(currentMove - 1);
8467     
8468             if (forwardMostMove != 0) {
8469                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8470                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8471                                                                 ) {
8472                     if (*appData.saveGameFile != NULLCHAR) {
8473                         SaveGameToFile(appData.saveGameFile, TRUE);
8474                     } else if (appData.autoSaveGames) {
8475                         AutoSaveGame();
8476                     }
8477                     if (*appData.savePositionFile != NULLCHAR) {
8478                         SavePositionToFile(appData.savePositionFile);
8479                     }
8480                 }
8481             }
8482
8483             /* Tell program how game ended in case it is learning */
8484             /* [HGM] Moved this to after saving the PGN, just in case */
8485             /* engine died and we got here through time loss. In that */
8486             /* case we will get a fatal error writing the pipe, which */
8487             /* would otherwise lose us the PGN.                       */
8488             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8489             /* output during GameEnds should never be fatal anymore   */
8490             if (gameMode == MachinePlaysWhite ||
8491                 gameMode == MachinePlaysBlack ||
8492                 gameMode == TwoMachinesPlay ||
8493                 gameMode == IcsPlayingWhite ||
8494                 gameMode == IcsPlayingBlack ||
8495                 gameMode == BeginningOfGame) {
8496                 char buf[MSG_SIZ];
8497                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8498                         resultDetails);
8499                 if (first.pr != NoProc) {
8500                     SendToProgram(buf, &first);
8501                 }
8502                 if (second.pr != NoProc &&
8503                     gameMode == TwoMachinesPlay) {
8504                     SendToProgram(buf, &second);
8505                 }
8506             }
8507         }
8508
8509         if (appData.icsActive) {
8510             if (appData.quietPlay &&
8511                 (gameMode == IcsPlayingWhite ||
8512                  gameMode == IcsPlayingBlack)) {
8513                 SendToICS(ics_prefix);
8514                 SendToICS("set shout 1\n");
8515             }
8516             nextGameMode = IcsIdle;
8517             ics_user_moved = FALSE;
8518             /* clean up premove.  It's ugly when the game has ended and the
8519              * premove highlights are still on the board.
8520              */
8521             if (gotPremove) {
8522               gotPremove = FALSE;
8523               ClearPremoveHighlights();
8524               DrawPosition(FALSE, boards[currentMove]);
8525             }
8526             if (whosays == GE_ICS) {
8527                 switch (result) {
8528                 case WhiteWins:
8529                     if (gameMode == IcsPlayingWhite)
8530                         PlayIcsWinSound();
8531                     else if(gameMode == IcsPlayingBlack)
8532                         PlayIcsLossSound();
8533                     break;
8534                 case BlackWins:
8535                     if (gameMode == IcsPlayingBlack)
8536                         PlayIcsWinSound();
8537                     else if(gameMode == IcsPlayingWhite)
8538                         PlayIcsLossSound();
8539                     break;
8540                 case GameIsDrawn:
8541                     PlayIcsDrawSound();
8542                     break;
8543                 default:
8544                     PlayIcsUnfinishedSound();
8545                 }
8546             }
8547         } else if (gameMode == EditGame ||
8548                    gameMode == PlayFromGameFile || 
8549                    gameMode == AnalyzeMode || 
8550                    gameMode == AnalyzeFile) {
8551             nextGameMode = gameMode;
8552         } else {
8553             nextGameMode = EndOfGame;
8554         }
8555         pausing = FALSE;
8556         ModeHighlight();
8557     } else {
8558         nextGameMode = gameMode;
8559     }
8560
8561     if (appData.noChessProgram) {
8562         gameMode = nextGameMode;
8563         ModeHighlight();
8564         endingGame = 0; /* [HGM] crash */
8565         return;
8566     }
8567
8568     if (first.reuse) {
8569         /* Put first chess program into idle state */
8570         if (first.pr != NoProc &&
8571             (gameMode == MachinePlaysWhite ||
8572              gameMode == MachinePlaysBlack ||
8573              gameMode == TwoMachinesPlay ||
8574              gameMode == IcsPlayingWhite ||
8575              gameMode == IcsPlayingBlack ||
8576              gameMode == BeginningOfGame)) {
8577             SendToProgram("force\n", &first);
8578             if (first.usePing) {
8579               char buf[MSG_SIZ];
8580               sprintf(buf, "ping %d\n", ++first.lastPing);
8581               SendToProgram(buf, &first);
8582             }
8583         }
8584     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8585         /* Kill off first chess program */
8586         if (first.isr != NULL)
8587           RemoveInputSource(first.isr);
8588         first.isr = NULL;
8589     
8590         if (first.pr != NoProc) {
8591             ExitAnalyzeMode();
8592             DoSleep( appData.delayBeforeQuit );
8593             SendToProgram("quit\n", &first);
8594             DoSleep( appData.delayAfterQuit );
8595             DestroyChildProcess(first.pr, first.useSigterm);
8596         }
8597         first.pr = NoProc;
8598     }
8599     if (second.reuse) {
8600         /* Put second chess program into idle state */
8601         if (second.pr != NoProc &&
8602             gameMode == TwoMachinesPlay) {
8603             SendToProgram("force\n", &second);
8604             if (second.usePing) {
8605               char buf[MSG_SIZ];
8606               sprintf(buf, "ping %d\n", ++second.lastPing);
8607               SendToProgram(buf, &second);
8608             }
8609         }
8610     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8611         /* Kill off second chess program */
8612         if (second.isr != NULL)
8613           RemoveInputSource(second.isr);
8614         second.isr = NULL;
8615     
8616         if (second.pr != NoProc) {
8617             DoSleep( appData.delayBeforeQuit );
8618             SendToProgram("quit\n", &second);
8619             DoSleep( appData.delayAfterQuit );
8620             DestroyChildProcess(second.pr, second.useSigterm);
8621         }
8622         second.pr = NoProc;
8623     }
8624
8625     if (matchMode && gameMode == TwoMachinesPlay) {
8626         switch (result) {
8627         case WhiteWins:
8628           if (first.twoMachinesColor[0] == 'w') {
8629             first.matchWins++;
8630           } else {
8631             second.matchWins++;
8632           }
8633           break;
8634         case BlackWins:
8635           if (first.twoMachinesColor[0] == 'b') {
8636             first.matchWins++;
8637           } else {
8638             second.matchWins++;
8639           }
8640           break;
8641         default:
8642           break;
8643         }
8644         if (matchGame < appData.matchGames) {
8645             char *tmp;
8646             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8647                 tmp = first.twoMachinesColor;
8648                 first.twoMachinesColor = second.twoMachinesColor;
8649                 second.twoMachinesColor = tmp;
8650             }
8651             gameMode = nextGameMode;
8652             matchGame++;
8653             if(appData.matchPause>10000 || appData.matchPause<10)
8654                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8655             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8656             endingGame = 0; /* [HGM] crash */
8657             return;
8658         } else {
8659             char buf[MSG_SIZ];
8660             gameMode = nextGameMode;
8661             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8662                     first.tidy, second.tidy,
8663                     first.matchWins, second.matchWins,
8664                     appData.matchGames - (first.matchWins + second.matchWins));
8665             DisplayFatalError(buf, 0, 0);
8666         }
8667     }
8668     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8669         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8670       ExitAnalyzeMode();
8671     gameMode = nextGameMode;
8672     ModeHighlight();
8673     endingGame = 0;  /* [HGM] crash */
8674 }
8675
8676 /* Assumes program was just initialized (initString sent).
8677    Leaves program in force mode. */
8678 void
8679 FeedMovesToProgram(cps, upto) 
8680      ChessProgramState *cps;
8681      int upto;
8682 {
8683     int i;
8684     
8685     if (appData.debugMode)
8686       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8687               startedFromSetupPosition ? "position and " : "",
8688               backwardMostMove, upto, cps->which);
8689     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8690         // [HGM] variantswitch: make engine aware of new variant
8691         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8692                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8693         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8694         SendToProgram(buf, cps);
8695         currentlyInitializedVariant = gameInfo.variant;
8696     }
8697     SendToProgram("force\n", cps);
8698     if (startedFromSetupPosition) {
8699         SendBoard(cps, backwardMostMove);
8700     if (appData.debugMode) {
8701         fprintf(debugFP, "feedMoves\n");
8702     }
8703     }
8704     for (i = backwardMostMove; i < upto; i++) {
8705         SendMoveToProgram(i, cps);
8706     }
8707 }
8708
8709
8710 void
8711 ResurrectChessProgram()
8712 {
8713      /* The chess program may have exited.
8714         If so, restart it and feed it all the moves made so far. */
8715
8716     if (appData.noChessProgram || first.pr != NoProc) return;
8717     
8718     StartChessProgram(&first);
8719     InitChessProgram(&first, FALSE);
8720     FeedMovesToProgram(&first, currentMove);
8721
8722     if (!first.sendTime) {
8723         /* can't tell gnuchess what its clock should read,
8724            so we bow to its notion. */
8725         ResetClocks();
8726         timeRemaining[0][currentMove] = whiteTimeRemaining;
8727         timeRemaining[1][currentMove] = blackTimeRemaining;
8728     }
8729
8730     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8731                 appData.icsEngineAnalyze) && first.analysisSupport) {
8732       SendToProgram("analyze\n", &first);
8733       first.analyzing = TRUE;
8734     }
8735 }
8736
8737 /*
8738  * Button procedures
8739  */
8740 void
8741 Reset(redraw, init)
8742      int redraw, init;
8743 {
8744     int i;
8745
8746     if (appData.debugMode) {
8747         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8748                 redraw, init, gameMode);
8749     }
8750     CleanupTail(); // [HGM] vari: delete any stored variations
8751     pausing = pauseExamInvalid = FALSE;
8752     startedFromSetupPosition = blackPlaysFirst = FALSE;
8753     firstMove = TRUE;
8754     whiteFlag = blackFlag = FALSE;
8755     userOfferedDraw = FALSE;
8756     hintRequested = bookRequested = FALSE;
8757     first.maybeThinking = FALSE;
8758     second.maybeThinking = FALSE;
8759     first.bookSuspend = FALSE; // [HGM] book
8760     second.bookSuspend = FALSE;
8761     thinkOutput[0] = NULLCHAR;
8762     lastHint[0] = NULLCHAR;
8763     ClearGameInfo(&gameInfo);
8764     gameInfo.variant = StringToVariant(appData.variant);
8765     ics_user_moved = ics_clock_paused = FALSE;
8766     ics_getting_history = H_FALSE;
8767     ics_gamenum = -1;
8768     white_holding[0] = black_holding[0] = NULLCHAR;
8769     ClearProgramStats();
8770     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8771     
8772     ResetFrontEnd();
8773     ClearHighlights();
8774     flipView = appData.flipView;
8775     ClearPremoveHighlights();
8776     gotPremove = FALSE;
8777     alarmSounded = FALSE;
8778
8779     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8780     if(appData.serverMovesName != NULL) {
8781         /* [HGM] prepare to make moves file for broadcasting */
8782         clock_t t = clock();
8783         if(serverMoves != NULL) fclose(serverMoves);
8784         serverMoves = fopen(appData.serverMovesName, "r");
8785         if(serverMoves != NULL) {
8786             fclose(serverMoves);
8787             /* delay 15 sec before overwriting, so all clients can see end */
8788             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8789         }
8790         serverMoves = fopen(appData.serverMovesName, "w");
8791     }
8792
8793     ExitAnalyzeMode();
8794     gameMode = BeginningOfGame;
8795     ModeHighlight();
8796     if(appData.icsActive) gameInfo.variant = VariantNormal;
8797     currentMove = forwardMostMove = backwardMostMove = 0;
8798     InitPosition(redraw);
8799     for (i = 0; i < MAX_MOVES; i++) {
8800         if (commentList[i] != NULL) {
8801             free(commentList[i]);
8802             commentList[i] = NULL;
8803         }
8804     }
8805     ResetClocks();
8806     timeRemaining[0][0] = whiteTimeRemaining;
8807     timeRemaining[1][0] = blackTimeRemaining;
8808     if (first.pr == NULL) {
8809         StartChessProgram(&first);
8810     }
8811     if (init) {
8812             InitChessProgram(&first, startedFromSetupPosition);
8813     }
8814     DisplayTitle("");
8815     DisplayMessage("", "");
8816     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8817     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8818 }
8819
8820 void
8821 AutoPlayGameLoop()
8822 {
8823     for (;;) {
8824         if (!AutoPlayOneMove())
8825           return;
8826         if (matchMode || appData.timeDelay == 0)
8827           continue;
8828         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8829           return;
8830         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8831         break;
8832     }
8833 }
8834
8835
8836 int
8837 AutoPlayOneMove()
8838 {
8839     int fromX, fromY, toX, toY;
8840
8841     if (appData.debugMode) {
8842       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8843     }
8844
8845     if (gameMode != PlayFromGameFile)
8846       return FALSE;
8847
8848     if (currentMove >= forwardMostMove) {
8849       gameMode = EditGame;
8850       ModeHighlight();
8851
8852       /* [AS] Clear current move marker at the end of a game */
8853       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8854
8855       return FALSE;
8856     }
8857     
8858     toX = moveList[currentMove][2] - AAA;
8859     toY = moveList[currentMove][3] - ONE;
8860
8861     if (moveList[currentMove][1] == '@') {
8862         if (appData.highlightLastMove) {
8863             SetHighlights(-1, -1, toX, toY);
8864         }
8865     } else {
8866         fromX = moveList[currentMove][0] - AAA;
8867         fromY = moveList[currentMove][1] - ONE;
8868
8869         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8870
8871         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8872
8873         if (appData.highlightLastMove) {
8874             SetHighlights(fromX, fromY, toX, toY);
8875         }
8876     }
8877     DisplayMove(currentMove);
8878     SendMoveToProgram(currentMove++, &first);
8879     DisplayBothClocks();
8880     DrawPosition(FALSE, boards[currentMove]);
8881     // [HGM] PV info: always display, routine tests if empty
8882     DisplayComment(currentMove - 1, commentList[currentMove]);
8883     return TRUE;
8884 }
8885
8886
8887 int
8888 LoadGameOneMove(readAhead)
8889      ChessMove readAhead;
8890 {
8891     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8892     char promoChar = NULLCHAR;
8893     ChessMove moveType;
8894     char move[MSG_SIZ];
8895     char *p, *q;
8896     
8897     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8898         gameMode != AnalyzeMode && gameMode != Training) {
8899         gameFileFP = NULL;
8900         return FALSE;
8901     }
8902     
8903     yyboardindex = forwardMostMove;
8904     if (readAhead != (ChessMove)0) {
8905       moveType = readAhead;
8906     } else {
8907       if (gameFileFP == NULL)
8908           return FALSE;
8909       moveType = (ChessMove) yylex();
8910     }
8911     
8912     done = FALSE;
8913     switch (moveType) {
8914       case Comment:
8915         if (appData.debugMode) 
8916           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8917         p = yy_text;
8918
8919         /* append the comment but don't display it */
8920         AppendComment(currentMove, p, FALSE);
8921         return TRUE;
8922
8923       case WhiteCapturesEnPassant:
8924       case BlackCapturesEnPassant:
8925       case WhitePromotionChancellor:
8926       case BlackPromotionChancellor:
8927       case WhitePromotionArchbishop:
8928       case BlackPromotionArchbishop:
8929       case WhitePromotionCentaur:
8930       case BlackPromotionCentaur:
8931       case WhitePromotionQueen:
8932       case BlackPromotionQueen:
8933       case WhitePromotionRook:
8934       case BlackPromotionRook:
8935       case WhitePromotionBishop:
8936       case BlackPromotionBishop:
8937       case WhitePromotionKnight:
8938       case BlackPromotionKnight:
8939       case WhitePromotionKing:
8940       case BlackPromotionKing:
8941       case NormalMove:
8942       case WhiteKingSideCastle:
8943       case WhiteQueenSideCastle:
8944       case BlackKingSideCastle:
8945       case BlackQueenSideCastle:
8946       case WhiteKingSideCastleWild:
8947       case WhiteQueenSideCastleWild:
8948       case BlackKingSideCastleWild:
8949       case BlackQueenSideCastleWild:
8950       /* PUSH Fabien */
8951       case WhiteHSideCastleFR:
8952       case WhiteASideCastleFR:
8953       case BlackHSideCastleFR:
8954       case BlackASideCastleFR:
8955       /* POP Fabien */
8956         if (appData.debugMode)
8957           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8958         fromX = currentMoveString[0] - AAA;
8959         fromY = currentMoveString[1] - ONE;
8960         toX = currentMoveString[2] - AAA;
8961         toY = currentMoveString[3] - ONE;
8962         promoChar = currentMoveString[4];
8963         break;
8964
8965       case WhiteDrop:
8966       case BlackDrop:
8967         if (appData.debugMode)
8968           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8969         fromX = moveType == WhiteDrop ?
8970           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8971         (int) CharToPiece(ToLower(currentMoveString[0]));
8972         fromY = DROP_RANK;
8973         toX = currentMoveString[2] - AAA;
8974         toY = currentMoveString[3] - ONE;
8975         break;
8976
8977       case WhiteWins:
8978       case BlackWins:
8979       case GameIsDrawn:
8980       case GameUnfinished:
8981         if (appData.debugMode)
8982           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8983         p = strchr(yy_text, '{');
8984         if (p == NULL) p = strchr(yy_text, '(');
8985         if (p == NULL) {
8986             p = yy_text;
8987             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8988         } else {
8989             q = strchr(p, *p == '{' ? '}' : ')');
8990             if (q != NULL) *q = NULLCHAR;
8991             p++;
8992         }
8993         GameEnds(moveType, p, GE_FILE);
8994         done = TRUE;
8995         if (cmailMsgLoaded) {
8996             ClearHighlights();
8997             flipView = WhiteOnMove(currentMove);
8998             if (moveType == GameUnfinished) flipView = !flipView;
8999             if (appData.debugMode)
9000               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9001         }
9002         break;
9003
9004       case (ChessMove) 0:       /* end of file */
9005         if (appData.debugMode)
9006           fprintf(debugFP, "Parser hit end of file\n");
9007         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9008           case MT_NONE:
9009           case MT_CHECK:
9010             break;
9011           case MT_CHECKMATE:
9012           case MT_STAINMATE:
9013             if (WhiteOnMove(currentMove)) {
9014                 GameEnds(BlackWins, "Black mates", GE_FILE);
9015             } else {
9016                 GameEnds(WhiteWins, "White mates", GE_FILE);
9017             }
9018             break;
9019           case MT_STALEMATE:
9020             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9021             break;
9022         }
9023         done = TRUE;
9024         break;
9025
9026       case MoveNumberOne:
9027         if (lastLoadGameStart == GNUChessGame) {
9028             /* GNUChessGames have numbers, but they aren't move numbers */
9029             if (appData.debugMode)
9030               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9031                       yy_text, (int) moveType);
9032             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9033         }
9034         /* else fall thru */
9035
9036       case XBoardGame:
9037       case GNUChessGame:
9038       case PGNTag:
9039         /* Reached start of next game in file */
9040         if (appData.debugMode)
9041           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9042         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9043           case MT_NONE:
9044           case MT_CHECK:
9045             break;
9046           case MT_CHECKMATE:
9047           case MT_STAINMATE:
9048             if (WhiteOnMove(currentMove)) {
9049                 GameEnds(BlackWins, "Black mates", GE_FILE);
9050             } else {
9051                 GameEnds(WhiteWins, "White mates", GE_FILE);
9052             }
9053             break;
9054           case MT_STALEMATE:
9055             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9056             break;
9057         }
9058         done = TRUE;
9059         break;
9060
9061       case PositionDiagram:     /* should not happen; ignore */
9062       case ElapsedTime:         /* ignore */
9063       case NAG:                 /* ignore */
9064         if (appData.debugMode)
9065           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9066                   yy_text, (int) moveType);
9067         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9068
9069       case IllegalMove:
9070         if (appData.testLegality) {
9071             if (appData.debugMode)
9072               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9073             sprintf(move, _("Illegal move: %d.%s%s"),
9074                     (forwardMostMove / 2) + 1,
9075                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9076             DisplayError(move, 0);
9077             done = TRUE;
9078         } else {
9079             if (appData.debugMode)
9080               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9081                       yy_text, currentMoveString);
9082             fromX = currentMoveString[0] - AAA;
9083             fromY = currentMoveString[1] - ONE;
9084             toX = currentMoveString[2] - AAA;
9085             toY = currentMoveString[3] - ONE;
9086             promoChar = currentMoveString[4];
9087         }
9088         break;
9089
9090       case AmbiguousMove:
9091         if (appData.debugMode)
9092           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9093         sprintf(move, _("Ambiguous move: %d.%s%s"),
9094                 (forwardMostMove / 2) + 1,
9095                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9096         DisplayError(move, 0);
9097         done = TRUE;
9098         break;
9099
9100       default:
9101       case ImpossibleMove:
9102         if (appData.debugMode)
9103           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9104         sprintf(move, _("Illegal move: %d.%s%s"),
9105                 (forwardMostMove / 2) + 1,
9106                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9107         DisplayError(move, 0);
9108         done = TRUE;
9109         break;
9110     }
9111
9112     if (done) {
9113         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9114             DrawPosition(FALSE, boards[currentMove]);
9115             DisplayBothClocks();
9116             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9117               DisplayComment(currentMove - 1, commentList[currentMove]);
9118         }
9119         (void) StopLoadGameTimer();
9120         gameFileFP = NULL;
9121         cmailOldMove = forwardMostMove;
9122         return FALSE;
9123     } else {
9124         /* currentMoveString is set as a side-effect of yylex */
9125         strcat(currentMoveString, "\n");
9126         strcpy(moveList[forwardMostMove], currentMoveString);
9127         
9128         thinkOutput[0] = NULLCHAR;
9129         MakeMove(fromX, fromY, toX, toY, promoChar);
9130         currentMove = forwardMostMove;
9131         return TRUE;
9132     }
9133 }
9134
9135 /* Load the nth game from the given file */
9136 int
9137 LoadGameFromFile(filename, n, title, useList)
9138      char *filename;
9139      int n;
9140      char *title;
9141      /*Boolean*/ int useList;
9142 {
9143     FILE *f;
9144     char buf[MSG_SIZ];
9145
9146     if (strcmp(filename, "-") == 0) {
9147         f = stdin;
9148         title = "stdin";
9149     } else {
9150         f = fopen(filename, "rb");
9151         if (f == NULL) {
9152           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9153             DisplayError(buf, errno);
9154             return FALSE;
9155         }
9156     }
9157     if (fseek(f, 0, 0) == -1) {
9158         /* f is not seekable; probably a pipe */
9159         useList = FALSE;
9160     }
9161     if (useList && n == 0) {
9162         int error = GameListBuild(f);
9163         if (error) {
9164             DisplayError(_("Cannot build game list"), error);
9165         } else if (!ListEmpty(&gameList) &&
9166                    ((ListGame *) gameList.tailPred)->number > 1) {
9167             GameListPopUp(f, title);
9168             return TRUE;
9169         }
9170         GameListDestroy();
9171         n = 1;
9172     }
9173     if (n == 0) n = 1;
9174     return LoadGame(f, n, title, FALSE);
9175 }
9176
9177
9178 void
9179 MakeRegisteredMove()
9180 {
9181     int fromX, fromY, toX, toY;
9182     char promoChar;
9183     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9184         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9185           case CMAIL_MOVE:
9186           case CMAIL_DRAW:
9187             if (appData.debugMode)
9188               fprintf(debugFP, "Restoring %s for game %d\n",
9189                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9190     
9191             thinkOutput[0] = NULLCHAR;
9192             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9193             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9194             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9195             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9196             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9197             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9198             MakeMove(fromX, fromY, toX, toY, promoChar);
9199             ShowMove(fromX, fromY, toX, toY);
9200               
9201             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9202               case MT_NONE:
9203               case MT_CHECK:
9204                 break;
9205                 
9206               case MT_CHECKMATE:
9207               case MT_STAINMATE:
9208                 if (WhiteOnMove(currentMove)) {
9209                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9210                 } else {
9211                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9212                 }
9213                 break;
9214                 
9215               case MT_STALEMATE:
9216                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9217                 break;
9218             }
9219
9220             break;
9221             
9222           case CMAIL_RESIGN:
9223             if (WhiteOnMove(currentMove)) {
9224                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9225             } else {
9226                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9227             }
9228             break;
9229             
9230           case CMAIL_ACCEPT:
9231             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9232             break;
9233               
9234           default:
9235             break;
9236         }
9237     }
9238
9239     return;
9240 }
9241
9242 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9243 int
9244 CmailLoadGame(f, gameNumber, title, useList)
9245      FILE *f;
9246      int gameNumber;
9247      char *title;
9248      int useList;
9249 {
9250     int retVal;
9251
9252     if (gameNumber > nCmailGames) {
9253         DisplayError(_("No more games in this message"), 0);
9254         return FALSE;
9255     }
9256     if (f == lastLoadGameFP) {
9257         int offset = gameNumber - lastLoadGameNumber;
9258         if (offset == 0) {
9259             cmailMsg[0] = NULLCHAR;
9260             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9261                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9262                 nCmailMovesRegistered--;
9263             }
9264             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9265             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9266                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9267             }
9268         } else {
9269             if (! RegisterMove()) return FALSE;
9270         }
9271     }
9272
9273     retVal = LoadGame(f, gameNumber, title, useList);
9274
9275     /* Make move registered during previous look at this game, if any */
9276     MakeRegisteredMove();
9277
9278     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9279         commentList[currentMove]
9280           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9281         DisplayComment(currentMove - 1, commentList[currentMove]);
9282     }
9283
9284     return retVal;
9285 }
9286
9287 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9288 int
9289 ReloadGame(offset)
9290      int offset;
9291 {
9292     int gameNumber = lastLoadGameNumber + offset;
9293     if (lastLoadGameFP == NULL) {
9294         DisplayError(_("No game has been loaded yet"), 0);
9295         return FALSE;
9296     }
9297     if (gameNumber <= 0) {
9298         DisplayError(_("Can't back up any further"), 0);
9299         return FALSE;
9300     }
9301     if (cmailMsgLoaded) {
9302         return CmailLoadGame(lastLoadGameFP, gameNumber,
9303                              lastLoadGameTitle, lastLoadGameUseList);
9304     } else {
9305         return LoadGame(lastLoadGameFP, gameNumber,
9306                         lastLoadGameTitle, lastLoadGameUseList);
9307     }
9308 }
9309
9310
9311
9312 /* Load the nth game from open file f */
9313 int
9314 LoadGame(f, gameNumber, title, useList)
9315      FILE *f;
9316      int gameNumber;
9317      char *title;
9318      int useList;
9319 {
9320     ChessMove cm;
9321     char buf[MSG_SIZ];
9322     int gn = gameNumber;
9323     ListGame *lg = NULL;
9324     int numPGNTags = 0;
9325     int err;
9326     GameMode oldGameMode;
9327     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9328
9329     if (appData.debugMode) 
9330         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9331
9332     if (gameMode == Training )
9333         SetTrainingModeOff();
9334
9335     oldGameMode = gameMode;
9336     if (gameMode != BeginningOfGame) {
9337       Reset(FALSE, TRUE);
9338     }
9339
9340     gameFileFP = f;
9341     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9342         fclose(lastLoadGameFP);
9343     }
9344
9345     if (useList) {
9346         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9347         
9348         if (lg) {
9349             fseek(f, lg->offset, 0);
9350             GameListHighlight(gameNumber);
9351             gn = 1;
9352         }
9353         else {
9354             DisplayError(_("Game number out of range"), 0);
9355             return FALSE;
9356         }
9357     } else {
9358         GameListDestroy();
9359         if (fseek(f, 0, 0) == -1) {
9360             if (f == lastLoadGameFP ?
9361                 gameNumber == lastLoadGameNumber + 1 :
9362                 gameNumber == 1) {
9363                 gn = 1;
9364             } else {
9365                 DisplayError(_("Can't seek on game file"), 0);
9366                 return FALSE;
9367             }
9368         }
9369     }
9370     lastLoadGameFP = f;
9371     lastLoadGameNumber = gameNumber;
9372     strcpy(lastLoadGameTitle, title);
9373     lastLoadGameUseList = useList;
9374
9375     yynewfile(f);
9376
9377     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9378       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9379                 lg->gameInfo.black);
9380             DisplayTitle(buf);
9381     } else if (*title != NULLCHAR) {
9382         if (gameNumber > 1) {
9383             sprintf(buf, "%s %d", title, gameNumber);
9384             DisplayTitle(buf);
9385         } else {
9386             DisplayTitle(title);
9387         }
9388     }
9389
9390     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9391         gameMode = PlayFromGameFile;
9392         ModeHighlight();
9393     }
9394
9395     currentMove = forwardMostMove = backwardMostMove = 0;
9396     CopyBoard(boards[0], initialPosition);
9397     StopClocks();
9398
9399     /*
9400      * Skip the first gn-1 games in the file.
9401      * Also skip over anything that precedes an identifiable 
9402      * start of game marker, to avoid being confused by 
9403      * garbage at the start of the file.  Currently 
9404      * recognized start of game markers are the move number "1",
9405      * the pattern "gnuchess .* game", the pattern
9406      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9407      * A game that starts with one of the latter two patterns
9408      * will also have a move number 1, possibly
9409      * following a position diagram.
9410      * 5-4-02: Let's try being more lenient and allowing a game to
9411      * start with an unnumbered move.  Does that break anything?
9412      */
9413     cm = lastLoadGameStart = (ChessMove) 0;
9414     while (gn > 0) {
9415         yyboardindex = forwardMostMove;
9416         cm = (ChessMove) yylex();
9417         switch (cm) {
9418           case (ChessMove) 0:
9419             if (cmailMsgLoaded) {
9420                 nCmailGames = CMAIL_MAX_GAMES - gn;
9421             } else {
9422                 Reset(TRUE, TRUE);
9423                 DisplayError(_("Game not found in file"), 0);
9424             }
9425             return FALSE;
9426
9427           case GNUChessGame:
9428           case XBoardGame:
9429             gn--;
9430             lastLoadGameStart = cm;
9431             break;
9432             
9433           case MoveNumberOne:
9434             switch (lastLoadGameStart) {
9435               case GNUChessGame:
9436               case XBoardGame:
9437               case PGNTag:
9438                 break;
9439               case MoveNumberOne:
9440               case (ChessMove) 0:
9441                 gn--;           /* count this game */
9442                 lastLoadGameStart = cm;
9443                 break;
9444               default:
9445                 /* impossible */
9446                 break;
9447             }
9448             break;
9449
9450           case PGNTag:
9451             switch (lastLoadGameStart) {
9452               case GNUChessGame:
9453               case PGNTag:
9454               case MoveNumberOne:
9455               case (ChessMove) 0:
9456                 gn--;           /* count this game */
9457                 lastLoadGameStart = cm;
9458                 break;
9459               case XBoardGame:
9460                 lastLoadGameStart = cm; /* game counted already */
9461                 break;
9462               default:
9463                 /* impossible */
9464                 break;
9465             }
9466             if (gn > 0) {
9467                 do {
9468                     yyboardindex = forwardMostMove;
9469                     cm = (ChessMove) yylex();
9470                 } while (cm == PGNTag || cm == Comment);
9471             }
9472             break;
9473
9474           case WhiteWins:
9475           case BlackWins:
9476           case GameIsDrawn:
9477             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9478                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9479                     != CMAIL_OLD_RESULT) {
9480                     nCmailResults ++ ;
9481                     cmailResult[  CMAIL_MAX_GAMES
9482                                 - gn - 1] = CMAIL_OLD_RESULT;
9483                 }
9484             }
9485             break;
9486
9487           case NormalMove:
9488             /* Only a NormalMove can be at the start of a game
9489              * without a position diagram. */
9490             if (lastLoadGameStart == (ChessMove) 0) {
9491               gn--;
9492               lastLoadGameStart = MoveNumberOne;
9493             }
9494             break;
9495
9496           default:
9497             break;
9498         }
9499     }
9500     
9501     if (appData.debugMode)
9502       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9503
9504     if (cm == XBoardGame) {
9505         /* Skip any header junk before position diagram and/or move 1 */
9506         for (;;) {
9507             yyboardindex = forwardMostMove;
9508             cm = (ChessMove) yylex();
9509
9510             if (cm == (ChessMove) 0 ||
9511                 cm == GNUChessGame || cm == XBoardGame) {
9512                 /* Empty game; pretend end-of-file and handle later */
9513                 cm = (ChessMove) 0;
9514                 break;
9515             }
9516
9517             if (cm == MoveNumberOne || cm == PositionDiagram ||
9518                 cm == PGNTag || cm == Comment)
9519               break;
9520         }
9521     } else if (cm == GNUChessGame) {
9522         if (gameInfo.event != NULL) {
9523             free(gameInfo.event);
9524         }
9525         gameInfo.event = StrSave(yy_text);
9526     }   
9527
9528     startedFromSetupPosition = FALSE;
9529     while (cm == PGNTag) {
9530         if (appData.debugMode) 
9531           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9532         err = ParsePGNTag(yy_text, &gameInfo);
9533         if (!err) numPGNTags++;
9534
9535         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9536         if(gameInfo.variant != oldVariant) {
9537             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9538             InitPosition(TRUE);
9539             oldVariant = gameInfo.variant;
9540             if (appData.debugMode) 
9541               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9542         }
9543
9544
9545         if (gameInfo.fen != NULL) {
9546           Board initial_position;
9547           startedFromSetupPosition = TRUE;
9548           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9549             Reset(TRUE, TRUE);
9550             DisplayError(_("Bad FEN position in file"), 0);
9551             return FALSE;
9552           }
9553           CopyBoard(boards[0], initial_position);
9554           if (blackPlaysFirst) {
9555             currentMove = forwardMostMove = backwardMostMove = 1;
9556             CopyBoard(boards[1], initial_position);
9557             strcpy(moveList[0], "");
9558             strcpy(parseList[0], "");
9559             timeRemaining[0][1] = whiteTimeRemaining;
9560             timeRemaining[1][1] = blackTimeRemaining;
9561             if (commentList[0] != NULL) {
9562               commentList[1] = commentList[0];
9563               commentList[0] = NULL;
9564             }
9565           } else {
9566             currentMove = forwardMostMove = backwardMostMove = 0;
9567           }
9568           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9569           {   int i;
9570               initialRulePlies = FENrulePlies;
9571               for( i=0; i< nrCastlingRights; i++ )
9572                   initialRights[i] = initial_position[CASTLING][i];
9573           }
9574           yyboardindex = forwardMostMove;
9575           free(gameInfo.fen);
9576           gameInfo.fen = NULL;
9577         }
9578
9579         yyboardindex = forwardMostMove;
9580         cm = (ChessMove) yylex();
9581
9582         /* Handle comments interspersed among the tags */
9583         while (cm == Comment) {
9584             char *p;
9585             if (appData.debugMode) 
9586               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9587             p = yy_text;
9588             AppendComment(currentMove, p, FALSE);
9589             yyboardindex = forwardMostMove;
9590             cm = (ChessMove) yylex();
9591         }
9592     }
9593
9594     /* don't rely on existence of Event tag since if game was
9595      * pasted from clipboard the Event tag may not exist
9596      */
9597     if (numPGNTags > 0){
9598         char *tags;
9599         if (gameInfo.variant == VariantNormal) {
9600           gameInfo.variant = StringToVariant(gameInfo.event);
9601         }
9602         if (!matchMode) {
9603           if( appData.autoDisplayTags ) {
9604             tags = PGNTags(&gameInfo);
9605             TagsPopUp(tags, CmailMsg());
9606             free(tags);
9607           }
9608         }
9609     } else {
9610         /* Make something up, but don't display it now */
9611         SetGameInfo();
9612         TagsPopDown();
9613     }
9614
9615     if (cm == PositionDiagram) {
9616         int i, j;
9617         char *p;
9618         Board initial_position;
9619
9620         if (appData.debugMode)
9621           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9622
9623         if (!startedFromSetupPosition) {
9624             p = yy_text;
9625             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9626               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9627                 switch (*p) {
9628                   case '[':
9629                   case '-':
9630                   case ' ':
9631                   case '\t':
9632                   case '\n':
9633                   case '\r':
9634                     break;
9635                   default:
9636                     initial_position[i][j++] = CharToPiece(*p);
9637                     break;
9638                 }
9639             while (*p == ' ' || *p == '\t' ||
9640                    *p == '\n' || *p == '\r') p++;
9641         
9642             if (strncmp(p, "black", strlen("black"))==0)
9643               blackPlaysFirst = TRUE;
9644             else
9645               blackPlaysFirst = FALSE;
9646             startedFromSetupPosition = TRUE;
9647         
9648             CopyBoard(boards[0], initial_position);
9649             if (blackPlaysFirst) {
9650                 currentMove = forwardMostMove = backwardMostMove = 1;
9651                 CopyBoard(boards[1], initial_position);
9652                 strcpy(moveList[0], "");
9653                 strcpy(parseList[0], "");
9654                 timeRemaining[0][1] = whiteTimeRemaining;
9655                 timeRemaining[1][1] = blackTimeRemaining;
9656                 if (commentList[0] != NULL) {
9657                     commentList[1] = commentList[0];
9658                     commentList[0] = NULL;
9659                 }
9660             } else {
9661                 currentMove = forwardMostMove = backwardMostMove = 0;
9662             }
9663         }
9664         yyboardindex = forwardMostMove;
9665         cm = (ChessMove) yylex();
9666     }
9667
9668     if (first.pr == NoProc) {
9669         StartChessProgram(&first);
9670     }
9671     InitChessProgram(&first, FALSE);
9672     SendToProgram("force\n", &first);
9673     if (startedFromSetupPosition) {
9674         SendBoard(&first, forwardMostMove);
9675     if (appData.debugMode) {
9676         fprintf(debugFP, "Load Game\n");
9677     }
9678         DisplayBothClocks();
9679     }      
9680
9681     /* [HGM] server: flag to write setup moves in broadcast file as one */
9682     loadFlag = appData.suppressLoadMoves;
9683
9684     while (cm == Comment) {
9685         char *p;
9686         if (appData.debugMode) 
9687           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9688         p = yy_text;
9689         AppendComment(currentMove, p, FALSE);
9690         yyboardindex = forwardMostMove;
9691         cm = (ChessMove) yylex();
9692     }
9693
9694     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9695         cm == WhiteWins || cm == BlackWins ||
9696         cm == GameIsDrawn || cm == GameUnfinished) {
9697         DisplayMessage("", _("No moves in game"));
9698         if (cmailMsgLoaded) {
9699             if (appData.debugMode)
9700               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9701             ClearHighlights();
9702             flipView = FALSE;
9703         }
9704         DrawPosition(FALSE, boards[currentMove]);
9705         DisplayBothClocks();
9706         gameMode = EditGame;
9707         ModeHighlight();
9708         gameFileFP = NULL;
9709         cmailOldMove = 0;
9710         return TRUE;
9711     }
9712
9713     // [HGM] PV info: routine tests if comment empty
9714     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9715         DisplayComment(currentMove - 1, commentList[currentMove]);
9716     }
9717     if (!matchMode && appData.timeDelay != 0) 
9718       DrawPosition(FALSE, boards[currentMove]);
9719
9720     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9721       programStats.ok_to_send = 1;
9722     }
9723
9724     /* if the first token after the PGN tags is a move
9725      * and not move number 1, retrieve it from the parser 
9726      */
9727     if (cm != MoveNumberOne)
9728         LoadGameOneMove(cm);
9729
9730     /* load the remaining moves from the file */
9731     while (LoadGameOneMove((ChessMove)0)) {
9732       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9733       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9734     }
9735
9736     /* rewind to the start of the game */
9737     currentMove = backwardMostMove;
9738
9739     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9740
9741     if (oldGameMode == AnalyzeFile ||
9742         oldGameMode == AnalyzeMode) {
9743       AnalyzeFileEvent();
9744     }
9745
9746     if (matchMode || appData.timeDelay == 0) {
9747       ToEndEvent();
9748       gameMode = EditGame;
9749       ModeHighlight();
9750     } else if (appData.timeDelay > 0) {
9751       AutoPlayGameLoop();
9752     }
9753
9754     if (appData.debugMode) 
9755         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9756
9757     loadFlag = 0; /* [HGM] true game starts */
9758     return TRUE;
9759 }
9760
9761 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9762 int
9763 ReloadPosition(offset)
9764      int offset;
9765 {
9766     int positionNumber = lastLoadPositionNumber + offset;
9767     if (lastLoadPositionFP == NULL) {
9768         DisplayError(_("No position has been loaded yet"), 0);
9769         return FALSE;
9770     }
9771     if (positionNumber <= 0) {
9772         DisplayError(_("Can't back up any further"), 0);
9773         return FALSE;
9774     }
9775     return LoadPosition(lastLoadPositionFP, positionNumber,
9776                         lastLoadPositionTitle);
9777 }
9778
9779 /* Load the nth position from the given file */
9780 int
9781 LoadPositionFromFile(filename, n, title)
9782      char *filename;
9783      int n;
9784      char *title;
9785 {
9786     FILE *f;
9787     char buf[MSG_SIZ];
9788
9789     if (strcmp(filename, "-") == 0) {
9790         return LoadPosition(stdin, n, "stdin");
9791     } else {
9792         f = fopen(filename, "rb");
9793         if (f == NULL) {
9794             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9795             DisplayError(buf, errno);
9796             return FALSE;
9797         } else {
9798             return LoadPosition(f, n, title);
9799         }
9800     }
9801 }
9802
9803 /* Load the nth position from the given open file, and close it */
9804 int
9805 LoadPosition(f, positionNumber, title)
9806      FILE *f;
9807      int positionNumber;
9808      char *title;
9809 {
9810     char *p, line[MSG_SIZ];
9811     Board initial_position;
9812     int i, j, fenMode, pn;
9813     
9814     if (gameMode == Training )
9815         SetTrainingModeOff();
9816
9817     if (gameMode != BeginningOfGame) {
9818         Reset(FALSE, TRUE);
9819     }
9820     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9821         fclose(lastLoadPositionFP);
9822     }
9823     if (positionNumber == 0) positionNumber = 1;
9824     lastLoadPositionFP = f;
9825     lastLoadPositionNumber = positionNumber;
9826     strcpy(lastLoadPositionTitle, title);
9827     if (first.pr == NoProc) {
9828       StartChessProgram(&first);
9829       InitChessProgram(&first, FALSE);
9830     }    
9831     pn = positionNumber;
9832     if (positionNumber < 0) {
9833         /* Negative position number means to seek to that byte offset */
9834         if (fseek(f, -positionNumber, 0) == -1) {
9835             DisplayError(_("Can't seek on position file"), 0);
9836             return FALSE;
9837         };
9838         pn = 1;
9839     } else {
9840         if (fseek(f, 0, 0) == -1) {
9841             if (f == lastLoadPositionFP ?
9842                 positionNumber == lastLoadPositionNumber + 1 :
9843                 positionNumber == 1) {
9844                 pn = 1;
9845             } else {
9846                 DisplayError(_("Can't seek on position file"), 0);
9847                 return FALSE;
9848             }
9849         }
9850     }
9851     /* See if this file is FEN or old-style xboard */
9852     if (fgets(line, MSG_SIZ, f) == NULL) {
9853         DisplayError(_("Position not found in file"), 0);
9854         return FALSE;
9855     }
9856     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9857     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9858
9859     if (pn >= 2) {
9860         if (fenMode || line[0] == '#') pn--;
9861         while (pn > 0) {
9862             /* skip positions before number pn */
9863             if (fgets(line, MSG_SIZ, f) == NULL) {
9864                 Reset(TRUE, TRUE);
9865                 DisplayError(_("Position not found in file"), 0);
9866                 return FALSE;
9867             }
9868             if (fenMode || line[0] == '#') pn--;
9869         }
9870     }
9871
9872     if (fenMode) {
9873         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9874             DisplayError(_("Bad FEN position in file"), 0);
9875             return FALSE;
9876         }
9877     } else {
9878         (void) fgets(line, MSG_SIZ, f);
9879         (void) fgets(line, MSG_SIZ, f);
9880     
9881         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9882             (void) fgets(line, MSG_SIZ, f);
9883             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9884                 if (*p == ' ')
9885                   continue;
9886                 initial_position[i][j++] = CharToPiece(*p);
9887             }
9888         }
9889     
9890         blackPlaysFirst = FALSE;
9891         if (!feof(f)) {
9892             (void) fgets(line, MSG_SIZ, f);
9893             if (strncmp(line, "black", strlen("black"))==0)
9894               blackPlaysFirst = TRUE;
9895         }
9896     }
9897     startedFromSetupPosition = TRUE;
9898     
9899     SendToProgram("force\n", &first);
9900     CopyBoard(boards[0], initial_position);
9901     if (blackPlaysFirst) {
9902         currentMove = forwardMostMove = backwardMostMove = 1;
9903         strcpy(moveList[0], "");
9904         strcpy(parseList[0], "");
9905         CopyBoard(boards[1], initial_position);
9906         DisplayMessage("", _("Black to play"));
9907     } else {
9908         currentMove = forwardMostMove = backwardMostMove = 0;
9909         DisplayMessage("", _("White to play"));
9910     }
9911     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9912     SendBoard(&first, forwardMostMove);
9913     if (appData.debugMode) {
9914 int i, j;
9915   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9916   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9917         fprintf(debugFP, "Load Position\n");
9918     }
9919
9920     if (positionNumber > 1) {
9921         sprintf(line, "%s %d", title, positionNumber);
9922         DisplayTitle(line);
9923     } else {
9924         DisplayTitle(title);
9925     }
9926     gameMode = EditGame;
9927     ModeHighlight();
9928     ResetClocks();
9929     timeRemaining[0][1] = whiteTimeRemaining;
9930     timeRemaining[1][1] = blackTimeRemaining;
9931     DrawPosition(FALSE, boards[currentMove]);
9932    
9933     return TRUE;
9934 }
9935
9936
9937 void
9938 CopyPlayerNameIntoFileName(dest, src)
9939      char **dest, *src;
9940 {
9941     while (*src != NULLCHAR && *src != ',') {
9942         if (*src == ' ') {
9943             *(*dest)++ = '_';
9944             src++;
9945         } else {
9946             *(*dest)++ = *src++;
9947         }
9948     }
9949 }
9950
9951 char *DefaultFileName(ext)
9952      char *ext;
9953 {
9954     static char def[MSG_SIZ];
9955     char *p;
9956
9957     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9958         p = def;
9959         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9960         *p++ = '-';
9961         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9962         *p++ = '.';
9963         strcpy(p, ext);
9964     } else {
9965         def[0] = NULLCHAR;
9966     }
9967     return def;
9968 }
9969
9970 /* Save the current game to the given file */
9971 int
9972 SaveGameToFile(filename, append)
9973      char *filename;
9974      int append;
9975 {
9976     FILE *f;
9977     char buf[MSG_SIZ];
9978
9979     if (strcmp(filename, "-") == 0) {
9980         return SaveGame(stdout, 0, NULL);
9981     } else {
9982         f = fopen(filename, append ? "a" : "w");
9983         if (f == NULL) {
9984             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9985             DisplayError(buf, errno);
9986             return FALSE;
9987         } else {
9988             return SaveGame(f, 0, NULL);
9989         }
9990     }
9991 }
9992
9993 char *
9994 SavePart(str)
9995      char *str;
9996 {
9997     static char buf[MSG_SIZ];
9998     char *p;
9999     
10000     p = strchr(str, ' ');
10001     if (p == NULL) return str;
10002     strncpy(buf, str, p - str);
10003     buf[p - str] = NULLCHAR;
10004     return buf;
10005 }
10006
10007 #define PGN_MAX_LINE 75
10008
10009 #define PGN_SIDE_WHITE  0
10010 #define PGN_SIDE_BLACK  1
10011
10012 /* [AS] */
10013 static int FindFirstMoveOutOfBook( int side )
10014 {
10015     int result = -1;
10016
10017     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10018         int index = backwardMostMove;
10019         int has_book_hit = 0;
10020
10021         if( (index % 2) != side ) {
10022             index++;
10023         }
10024
10025         while( index < forwardMostMove ) {
10026             /* Check to see if engine is in book */
10027             int depth = pvInfoList[index].depth;
10028             int score = pvInfoList[index].score;
10029             int in_book = 0;
10030
10031             if( depth <= 2 ) {
10032                 in_book = 1;
10033             }
10034             else if( score == 0 && depth == 63 ) {
10035                 in_book = 1; /* Zappa */
10036             }
10037             else if( score == 2 && depth == 99 ) {
10038                 in_book = 1; /* Abrok */
10039             }
10040
10041             has_book_hit += in_book;
10042
10043             if( ! in_book ) {
10044                 result = index;
10045
10046                 break;
10047             }
10048
10049             index += 2;
10050         }
10051     }
10052
10053     return result;
10054 }
10055
10056 /* [AS] */
10057 void GetOutOfBookInfo( char * buf )
10058 {
10059     int oob[2];
10060     int i;
10061     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10062
10063     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10064     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10065
10066     *buf = '\0';
10067
10068     if( oob[0] >= 0 || oob[1] >= 0 ) {
10069         for( i=0; i<2; i++ ) {
10070             int idx = oob[i];
10071
10072             if( idx >= 0 ) {
10073                 if( i > 0 && oob[0] >= 0 ) {
10074                     strcat( buf, "   " );
10075                 }
10076
10077                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10078                 sprintf( buf+strlen(buf), "%s%.2f", 
10079                     pvInfoList[idx].score >= 0 ? "+" : "",
10080                     pvInfoList[idx].score / 100.0 );
10081             }
10082         }
10083     }
10084 }
10085
10086 /* Save game in PGN style and close the file */
10087 int
10088 SaveGamePGN(f)
10089      FILE *f;
10090 {
10091     int i, offset, linelen, newblock;
10092     time_t tm;
10093 //    char *movetext;
10094     char numtext[32];
10095     int movelen, numlen, blank;
10096     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10097
10098     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10099     
10100     tm = time((time_t *) NULL);
10101     
10102     PrintPGNTags(f, &gameInfo);
10103     
10104     if (backwardMostMove > 0 || startedFromSetupPosition) {
10105         char *fen = PositionToFEN(backwardMostMove, NULL);
10106         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10107         fprintf(f, "\n{--------------\n");
10108         PrintPosition(f, backwardMostMove);
10109         fprintf(f, "--------------}\n");
10110         free(fen);
10111     }
10112     else {
10113         /* [AS] Out of book annotation */
10114         if( appData.saveOutOfBookInfo ) {
10115             char buf[64];
10116
10117             GetOutOfBookInfo( buf );
10118
10119             if( buf[0] != '\0' ) {
10120                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10121             }
10122         }
10123
10124         fprintf(f, "\n");
10125     }
10126
10127     i = backwardMostMove;
10128     linelen = 0;
10129     newblock = TRUE;
10130
10131     while (i < forwardMostMove) {
10132         /* Print comments preceding this move */
10133         if (commentList[i] != NULL) {
10134             if (linelen > 0) fprintf(f, "\n");
10135             fprintf(f, "%s", commentList[i]);
10136             linelen = 0;
10137             newblock = TRUE;
10138         }
10139
10140         /* Format move number */
10141         if ((i % 2) == 0) {
10142             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10143         } else {
10144             if (newblock) {
10145                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10146             } else {
10147                 numtext[0] = NULLCHAR;
10148             }
10149         }
10150         numlen = strlen(numtext);
10151         newblock = FALSE;
10152
10153         /* Print move number */
10154         blank = linelen > 0 && numlen > 0;
10155         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10156             fprintf(f, "\n");
10157             linelen = 0;
10158             blank = 0;
10159         }
10160         if (blank) {
10161             fprintf(f, " ");
10162             linelen++;
10163         }
10164         fprintf(f, "%s", numtext);
10165         linelen += numlen;
10166
10167         /* Get move */
10168         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10169         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10170
10171         /* Print move */
10172         blank = linelen > 0 && movelen > 0;
10173         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10174             fprintf(f, "\n");
10175             linelen = 0;
10176             blank = 0;
10177         }
10178         if (blank) {
10179             fprintf(f, " ");
10180             linelen++;
10181         }
10182         fprintf(f, "%s", move_buffer);
10183         linelen += movelen;
10184
10185         /* [AS] Add PV info if present */
10186         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10187             /* [HGM] add time */
10188             char buf[MSG_SIZ]; int seconds;
10189
10190             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10191
10192             if( seconds <= 0) buf[0] = 0; else
10193             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10194                 seconds = (seconds + 4)/10; // round to full seconds
10195                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10196                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10197             }
10198
10199             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10200                 pvInfoList[i].score >= 0 ? "+" : "",
10201                 pvInfoList[i].score / 100.0,
10202                 pvInfoList[i].depth,
10203                 buf );
10204
10205             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10206
10207             /* Print score/depth */
10208             blank = linelen > 0 && movelen > 0;
10209             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10210                 fprintf(f, "\n");
10211                 linelen = 0;
10212                 blank = 0;
10213             }
10214             if (blank) {
10215                 fprintf(f, " ");
10216                 linelen++;
10217             }
10218             fprintf(f, "%s", move_buffer);
10219             linelen += movelen;
10220         }
10221
10222         i++;
10223     }
10224     
10225     /* Start a new line */
10226     if (linelen > 0) fprintf(f, "\n");
10227
10228     /* Print comments after last move */
10229     if (commentList[i] != NULL) {
10230         fprintf(f, "%s\n", commentList[i]);
10231     }
10232
10233     /* Print result */
10234     if (gameInfo.resultDetails != NULL &&
10235         gameInfo.resultDetails[0] != NULLCHAR) {
10236         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10237                 PGNResult(gameInfo.result));
10238     } else {
10239         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10240     }
10241
10242     fclose(f);
10243     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10244     return TRUE;
10245 }
10246
10247 /* Save game in old style and close the file */
10248 int
10249 SaveGameOldStyle(f)
10250      FILE *f;
10251 {
10252     int i, offset;
10253     time_t tm;
10254     
10255     tm = time((time_t *) NULL);
10256     
10257     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10258     PrintOpponents(f);
10259     
10260     if (backwardMostMove > 0 || startedFromSetupPosition) {
10261         fprintf(f, "\n[--------------\n");
10262         PrintPosition(f, backwardMostMove);
10263         fprintf(f, "--------------]\n");
10264     } else {
10265         fprintf(f, "\n");
10266     }
10267
10268     i = backwardMostMove;
10269     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10270
10271     while (i < forwardMostMove) {
10272         if (commentList[i] != NULL) {
10273             fprintf(f, "[%s]\n", commentList[i]);
10274         }
10275
10276         if ((i % 2) == 1) {
10277             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10278             i++;
10279         } else {
10280             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10281             i++;
10282             if (commentList[i] != NULL) {
10283                 fprintf(f, "\n");
10284                 continue;
10285             }
10286             if (i >= forwardMostMove) {
10287                 fprintf(f, "\n");
10288                 break;
10289             }
10290             fprintf(f, "%s\n", parseList[i]);
10291             i++;
10292         }
10293     }
10294     
10295     if (commentList[i] != NULL) {
10296         fprintf(f, "[%s]\n", commentList[i]);
10297     }
10298
10299     /* This isn't really the old style, but it's close enough */
10300     if (gameInfo.resultDetails != NULL &&
10301         gameInfo.resultDetails[0] != NULLCHAR) {
10302         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10303                 gameInfo.resultDetails);
10304     } else {
10305         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10306     }
10307
10308     fclose(f);
10309     return TRUE;
10310 }
10311
10312 /* Save the current game to open file f and close the file */
10313 int
10314 SaveGame(f, dummy, dummy2)
10315      FILE *f;
10316      int dummy;
10317      char *dummy2;
10318 {
10319     if (gameMode == EditPosition) EditPositionDone(TRUE);
10320     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10321     if (appData.oldSaveStyle)
10322       return SaveGameOldStyle(f);
10323     else
10324       return SaveGamePGN(f);
10325 }
10326
10327 /* Save the current position to the given file */
10328 int
10329 SavePositionToFile(filename)
10330      char *filename;
10331 {
10332     FILE *f;
10333     char buf[MSG_SIZ];
10334
10335     if (strcmp(filename, "-") == 0) {
10336         return SavePosition(stdout, 0, NULL);
10337     } else {
10338         f = fopen(filename, "a");
10339         if (f == NULL) {
10340             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10341             DisplayError(buf, errno);
10342             return FALSE;
10343         } else {
10344             SavePosition(f, 0, NULL);
10345             return TRUE;
10346         }
10347     }
10348 }
10349
10350 /* Save the current position to the given open file and close the file */
10351 int
10352 SavePosition(f, dummy, dummy2)
10353      FILE *f;
10354      int dummy;
10355      char *dummy2;
10356 {
10357     time_t tm;
10358     char *fen;
10359     
10360     if (gameMode == EditPosition) EditPositionDone(TRUE);
10361     if (appData.oldSaveStyle) {
10362         tm = time((time_t *) NULL);
10363     
10364         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10365         PrintOpponents(f);
10366         fprintf(f, "[--------------\n");
10367         PrintPosition(f, currentMove);
10368         fprintf(f, "--------------]\n");
10369     } else {
10370         fen = PositionToFEN(currentMove, NULL);
10371         fprintf(f, "%s\n", fen);
10372         free(fen);
10373     }
10374     fclose(f);
10375     return TRUE;
10376 }
10377
10378 void
10379 ReloadCmailMsgEvent(unregister)
10380      int unregister;
10381 {
10382 #if !WIN32
10383     static char *inFilename = NULL;
10384     static char *outFilename;
10385     int i;
10386     struct stat inbuf, outbuf;
10387     int status;
10388     
10389     /* Any registered moves are unregistered if unregister is set, */
10390     /* i.e. invoked by the signal handler */
10391     if (unregister) {
10392         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10393             cmailMoveRegistered[i] = FALSE;
10394             if (cmailCommentList[i] != NULL) {
10395                 free(cmailCommentList[i]);
10396                 cmailCommentList[i] = NULL;
10397             }
10398         }
10399         nCmailMovesRegistered = 0;
10400     }
10401
10402     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10403         cmailResult[i] = CMAIL_NOT_RESULT;
10404     }
10405     nCmailResults = 0;
10406
10407     if (inFilename == NULL) {
10408         /* Because the filenames are static they only get malloced once  */
10409         /* and they never get freed                                      */
10410         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10411         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10412
10413         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10414         sprintf(outFilename, "%s.out", appData.cmailGameName);
10415     }
10416     
10417     status = stat(outFilename, &outbuf);
10418     if (status < 0) {
10419         cmailMailedMove = FALSE;
10420     } else {
10421         status = stat(inFilename, &inbuf);
10422         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10423     }
10424     
10425     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10426        counts the games, notes how each one terminated, etc.
10427        
10428        It would be nice to remove this kludge and instead gather all
10429        the information while building the game list.  (And to keep it
10430        in the game list nodes instead of having a bunch of fixed-size
10431        parallel arrays.)  Note this will require getting each game's
10432        termination from the PGN tags, as the game list builder does
10433        not process the game moves.  --mann
10434        */
10435     cmailMsgLoaded = TRUE;
10436     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10437     
10438     /* Load first game in the file or popup game menu */
10439     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10440
10441 #endif /* !WIN32 */
10442     return;
10443 }
10444
10445 int
10446 RegisterMove()
10447 {
10448     FILE *f;
10449     char string[MSG_SIZ];
10450
10451     if (   cmailMailedMove
10452         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10453         return TRUE;            /* Allow free viewing  */
10454     }
10455
10456     /* Unregister move to ensure that we don't leave RegisterMove        */
10457     /* with the move registered when the conditions for registering no   */
10458     /* longer hold                                                       */
10459     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10460         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10461         nCmailMovesRegistered --;
10462
10463         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10464           {
10465               free(cmailCommentList[lastLoadGameNumber - 1]);
10466               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10467           }
10468     }
10469
10470     if (cmailOldMove == -1) {
10471         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10472         return FALSE;
10473     }
10474
10475     if (currentMove > cmailOldMove + 1) {
10476         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10477         return FALSE;
10478     }
10479
10480     if (currentMove < cmailOldMove) {
10481         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10482         return FALSE;
10483     }
10484
10485     if (forwardMostMove > currentMove) {
10486         /* Silently truncate extra moves */
10487         TruncateGame();
10488     }
10489
10490     if (   (currentMove == cmailOldMove + 1)
10491         || (   (currentMove == cmailOldMove)
10492             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10493                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10494         if (gameInfo.result != GameUnfinished) {
10495             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10496         }
10497
10498         if (commentList[currentMove] != NULL) {
10499             cmailCommentList[lastLoadGameNumber - 1]
10500               = StrSave(commentList[currentMove]);
10501         }
10502         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10503
10504         if (appData.debugMode)
10505           fprintf(debugFP, "Saving %s for game %d\n",
10506                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10507
10508         sprintf(string,
10509                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10510         
10511         f = fopen(string, "w");
10512         if (appData.oldSaveStyle) {
10513             SaveGameOldStyle(f); /* also closes the file */
10514             
10515             sprintf(string, "%s.pos.out", appData.cmailGameName);
10516             f = fopen(string, "w");
10517             SavePosition(f, 0, NULL); /* also closes the file */
10518         } else {
10519             fprintf(f, "{--------------\n");
10520             PrintPosition(f, currentMove);
10521             fprintf(f, "--------------}\n\n");
10522             
10523             SaveGame(f, 0, NULL); /* also closes the file*/
10524         }
10525         
10526         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10527         nCmailMovesRegistered ++;
10528     } else if (nCmailGames == 1) {
10529         DisplayError(_("You have not made a move yet"), 0);
10530         return FALSE;
10531     }
10532
10533     return TRUE;
10534 }
10535
10536 void
10537 MailMoveEvent()
10538 {
10539 #if !WIN32
10540     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10541     FILE *commandOutput;
10542     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10543     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10544     int nBuffers;
10545     int i;
10546     int archived;
10547     char *arcDir;
10548
10549     if (! cmailMsgLoaded) {
10550         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10551         return;
10552     }
10553
10554     if (nCmailGames == nCmailResults) {
10555         DisplayError(_("No unfinished games"), 0);
10556         return;
10557     }
10558
10559 #if CMAIL_PROHIBIT_REMAIL
10560     if (cmailMailedMove) {
10561         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);
10562         DisplayError(msg, 0);
10563         return;
10564     }
10565 #endif
10566
10567     if (! (cmailMailedMove || RegisterMove())) return;
10568     
10569     if (   cmailMailedMove
10570         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10571         sprintf(string, partCommandString,
10572                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10573         commandOutput = popen(string, "r");
10574
10575         if (commandOutput == NULL) {
10576             DisplayError(_("Failed to invoke cmail"), 0);
10577         } else {
10578             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10579                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10580             }
10581             if (nBuffers > 1) {
10582                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10583                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10584                 nBytes = MSG_SIZ - 1;
10585             } else {
10586                 (void) memcpy(msg, buffer, nBytes);
10587             }
10588             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10589
10590             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10591                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10592
10593                 archived = TRUE;
10594                 for (i = 0; i < nCmailGames; i ++) {
10595                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10596                         archived = FALSE;
10597                     }
10598                 }
10599                 if (   archived
10600                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10601                         != NULL)) {
10602                     sprintf(buffer, "%s/%s.%s.archive",
10603                             arcDir,
10604                             appData.cmailGameName,
10605                             gameInfo.date);
10606                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10607                     cmailMsgLoaded = FALSE;
10608                 }
10609             }
10610
10611             DisplayInformation(msg);
10612             pclose(commandOutput);
10613         }
10614     } else {
10615         if ((*cmailMsg) != '\0') {
10616             DisplayInformation(cmailMsg);
10617         }
10618     }
10619
10620     return;
10621 #endif /* !WIN32 */
10622 }
10623
10624 char *
10625 CmailMsg()
10626 {
10627 #if WIN32
10628     return NULL;
10629 #else
10630     int  prependComma = 0;
10631     char number[5];
10632     char string[MSG_SIZ];       /* Space for game-list */
10633     int  i;
10634     
10635     if (!cmailMsgLoaded) return "";
10636
10637     if (cmailMailedMove) {
10638         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10639     } else {
10640         /* Create a list of games left */
10641         sprintf(string, "[");
10642         for (i = 0; i < nCmailGames; i ++) {
10643             if (! (   cmailMoveRegistered[i]
10644                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10645                 if (prependComma) {
10646                     sprintf(number, ",%d", i + 1);
10647                 } else {
10648                     sprintf(number, "%d", i + 1);
10649                     prependComma = 1;
10650                 }
10651                 
10652                 strcat(string, number);
10653             }
10654         }
10655         strcat(string, "]");
10656
10657         if (nCmailMovesRegistered + nCmailResults == 0) {
10658             switch (nCmailGames) {
10659               case 1:
10660                 sprintf(cmailMsg,
10661                         _("Still need to make move for game\n"));
10662                 break;
10663                 
10664               case 2:
10665                 sprintf(cmailMsg,
10666                         _("Still need to make moves for both games\n"));
10667                 break;
10668                 
10669               default:
10670                 sprintf(cmailMsg,
10671                         _("Still need to make moves for all %d games\n"),
10672                         nCmailGames);
10673                 break;
10674             }
10675         } else {
10676             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10677               case 1:
10678                 sprintf(cmailMsg,
10679                         _("Still need to make a move for game %s\n"),
10680                         string);
10681                 break;
10682                 
10683               case 0:
10684                 if (nCmailResults == nCmailGames) {
10685                     sprintf(cmailMsg, _("No unfinished games\n"));
10686                 } else {
10687                     sprintf(cmailMsg, _("Ready to send mail\n"));
10688                 }
10689                 break;
10690                 
10691               default:
10692                 sprintf(cmailMsg,
10693                         _("Still need to make moves for games %s\n"),
10694                         string);
10695             }
10696         }
10697     }
10698     return cmailMsg;
10699 #endif /* WIN32 */
10700 }
10701
10702 void
10703 ResetGameEvent()
10704 {
10705     if (gameMode == Training)
10706       SetTrainingModeOff();
10707
10708     Reset(TRUE, TRUE);
10709     cmailMsgLoaded = FALSE;
10710     if (appData.icsActive) {
10711       SendToICS(ics_prefix);
10712       SendToICS("refresh\n");
10713     }
10714 }
10715
10716 void
10717 ExitEvent(status)
10718      int status;
10719 {
10720     exiting++;
10721     if (exiting > 2) {
10722       /* Give up on clean exit */
10723       exit(status);
10724     }
10725     if (exiting > 1) {
10726       /* Keep trying for clean exit */
10727       return;
10728     }
10729
10730     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10731
10732     if (telnetISR != NULL) {
10733       RemoveInputSource(telnetISR);
10734     }
10735     if (icsPR != NoProc) {
10736       DestroyChildProcess(icsPR, TRUE);
10737     }
10738
10739     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10740     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10741
10742     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10743     /* make sure this other one finishes before killing it!                  */
10744     if(endingGame) { int count = 0;
10745         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10746         while(endingGame && count++ < 10) DoSleep(1);
10747         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10748     }
10749
10750     /* Kill off chess programs */
10751     if (first.pr != NoProc) {
10752         ExitAnalyzeMode();
10753         
10754         DoSleep( appData.delayBeforeQuit );
10755         SendToProgram("quit\n", &first);
10756         DoSleep( appData.delayAfterQuit );
10757         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10758     }
10759     if (second.pr != NoProc) {
10760         DoSleep( appData.delayBeforeQuit );
10761         SendToProgram("quit\n", &second);
10762         DoSleep( appData.delayAfterQuit );
10763         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10764     }
10765     if (first.isr != NULL) {
10766         RemoveInputSource(first.isr);
10767     }
10768     if (second.isr != NULL) {
10769         RemoveInputSource(second.isr);
10770     }
10771
10772     ShutDownFrontEnd();
10773     exit(status);
10774 }
10775
10776 void
10777 PauseEvent()
10778 {
10779     if (appData.debugMode)
10780         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10781     if (pausing) {
10782         pausing = FALSE;
10783         ModeHighlight();
10784         if (gameMode == MachinePlaysWhite ||
10785             gameMode == MachinePlaysBlack) {
10786             StartClocks();
10787         } else {
10788             DisplayBothClocks();
10789         }
10790         if (gameMode == PlayFromGameFile) {
10791             if (appData.timeDelay >= 0) 
10792                 AutoPlayGameLoop();
10793         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10794             Reset(FALSE, TRUE);
10795             SendToICS(ics_prefix);
10796             SendToICS("refresh\n");
10797         } else if (currentMove < forwardMostMove) {
10798             ForwardInner(forwardMostMove);
10799         }
10800         pauseExamInvalid = FALSE;
10801     } else {
10802         switch (gameMode) {
10803           default:
10804             return;
10805           case IcsExamining:
10806             pauseExamForwardMostMove = forwardMostMove;
10807             pauseExamInvalid = FALSE;
10808             /* fall through */
10809           case IcsObserving:
10810           case IcsPlayingWhite:
10811           case IcsPlayingBlack:
10812             pausing = TRUE;
10813             ModeHighlight();
10814             return;
10815           case PlayFromGameFile:
10816             (void) StopLoadGameTimer();
10817             pausing = TRUE;
10818             ModeHighlight();
10819             break;
10820           case BeginningOfGame:
10821             if (appData.icsActive) return;
10822             /* else fall through */
10823           case MachinePlaysWhite:
10824           case MachinePlaysBlack:
10825           case TwoMachinesPlay:
10826             if (forwardMostMove == 0)
10827               return;           /* don't pause if no one has moved */
10828             if ((gameMode == MachinePlaysWhite &&
10829                  !WhiteOnMove(forwardMostMove)) ||
10830                 (gameMode == MachinePlaysBlack &&
10831                  WhiteOnMove(forwardMostMove))) {
10832                 StopClocks();
10833             }
10834             pausing = TRUE;
10835             ModeHighlight();
10836             break;
10837         }
10838     }
10839 }
10840
10841 void
10842 EditCommentEvent()
10843 {
10844     char title[MSG_SIZ];
10845
10846     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10847         strcpy(title, _("Edit comment"));
10848     } else {
10849         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10850                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10851                 parseList[currentMove - 1]);
10852     }
10853
10854     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10855 }
10856
10857
10858 void
10859 EditTagsEvent()
10860 {
10861     char *tags = PGNTags(&gameInfo);
10862     EditTagsPopUp(tags);
10863     free(tags);
10864 }
10865
10866 void
10867 AnalyzeModeEvent()
10868 {
10869     if (appData.noChessProgram || gameMode == AnalyzeMode)
10870       return;
10871
10872     if (gameMode != AnalyzeFile) {
10873         if (!appData.icsEngineAnalyze) {
10874                EditGameEvent();
10875                if (gameMode != EditGame) return;
10876         }
10877         ResurrectChessProgram();
10878         SendToProgram("analyze\n", &first);
10879         first.analyzing = TRUE;
10880         /*first.maybeThinking = TRUE;*/
10881         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10882         EngineOutputPopUp();
10883     }
10884     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10885     pausing = FALSE;
10886     ModeHighlight();
10887     SetGameInfo();
10888
10889     StartAnalysisClock();
10890     GetTimeMark(&lastNodeCountTime);
10891     lastNodeCount = 0;
10892 }
10893
10894 void
10895 AnalyzeFileEvent()
10896 {
10897     if (appData.noChessProgram || gameMode == AnalyzeFile)
10898       return;
10899
10900     if (gameMode != AnalyzeMode) {
10901         EditGameEvent();
10902         if (gameMode != EditGame) return;
10903         ResurrectChessProgram();
10904         SendToProgram("analyze\n", &first);
10905         first.analyzing = TRUE;
10906         /*first.maybeThinking = TRUE;*/
10907         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10908         EngineOutputPopUp();
10909     }
10910     gameMode = AnalyzeFile;
10911     pausing = FALSE;
10912     ModeHighlight();
10913     SetGameInfo();
10914
10915     StartAnalysisClock();
10916     GetTimeMark(&lastNodeCountTime);
10917     lastNodeCount = 0;
10918 }
10919
10920 void
10921 MachineWhiteEvent()
10922 {
10923     char buf[MSG_SIZ];
10924     char *bookHit = NULL;
10925
10926     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10927       return;
10928
10929
10930     if (gameMode == PlayFromGameFile || 
10931         gameMode == TwoMachinesPlay  || 
10932         gameMode == Training         || 
10933         gameMode == AnalyzeMode      || 
10934         gameMode == EndOfGame)
10935         EditGameEvent();
10936
10937     if (gameMode == EditPosition) 
10938         EditPositionDone(TRUE);
10939
10940     if (!WhiteOnMove(currentMove)) {
10941         DisplayError(_("It is not White's turn"), 0);
10942         return;
10943     }
10944   
10945     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10946       ExitAnalyzeMode();
10947
10948     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10949         gameMode == AnalyzeFile)
10950         TruncateGame();
10951
10952     ResurrectChessProgram();    /* in case it isn't running */
10953     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10954         gameMode = MachinePlaysWhite;
10955         ResetClocks();
10956     } else
10957     gameMode = MachinePlaysWhite;
10958     pausing = FALSE;
10959     ModeHighlight();
10960     SetGameInfo();
10961     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10962     DisplayTitle(buf);
10963     if (first.sendName) {
10964       sprintf(buf, "name %s\n", gameInfo.black);
10965       SendToProgram(buf, &first);
10966     }
10967     if (first.sendTime) {
10968       if (first.useColors) {
10969         SendToProgram("black\n", &first); /*gnu kludge*/
10970       }
10971       SendTimeRemaining(&first, TRUE);
10972     }
10973     if (first.useColors) {
10974       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10975     }
10976     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10977     SetMachineThinkingEnables();
10978     first.maybeThinking = TRUE;
10979     StartClocks();
10980     firstMove = FALSE;
10981
10982     if (appData.autoFlipView && !flipView) {
10983       flipView = !flipView;
10984       DrawPosition(FALSE, NULL);
10985       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10986     }
10987
10988     if(bookHit) { // [HGM] book: simulate book reply
10989         static char bookMove[MSG_SIZ]; // a bit generous?
10990
10991         programStats.nodes = programStats.depth = programStats.time = 
10992         programStats.score = programStats.got_only_move = 0;
10993         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10994
10995         strcpy(bookMove, "move ");
10996         strcat(bookMove, bookHit);
10997         HandleMachineMove(bookMove, &first);
10998     }
10999 }
11000
11001 void
11002 MachineBlackEvent()
11003 {
11004     char buf[MSG_SIZ];
11005    char *bookHit = NULL;
11006
11007     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11008         return;
11009
11010
11011     if (gameMode == PlayFromGameFile || 
11012         gameMode == TwoMachinesPlay  || 
11013         gameMode == Training         || 
11014         gameMode == AnalyzeMode      || 
11015         gameMode == EndOfGame)
11016         EditGameEvent();
11017
11018     if (gameMode == EditPosition) 
11019         EditPositionDone(TRUE);
11020
11021     if (WhiteOnMove(currentMove)) {
11022         DisplayError(_("It is not Black's turn"), 0);
11023         return;
11024     }
11025     
11026     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11027       ExitAnalyzeMode();
11028
11029     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11030         gameMode == AnalyzeFile)
11031         TruncateGame();
11032
11033     ResurrectChessProgram();    /* in case it isn't running */
11034     gameMode = MachinePlaysBlack;
11035     pausing = FALSE;
11036     ModeHighlight();
11037     SetGameInfo();
11038     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11039     DisplayTitle(buf);
11040     if (first.sendName) {
11041       sprintf(buf, "name %s\n", gameInfo.white);
11042       SendToProgram(buf, &first);
11043     }
11044     if (first.sendTime) {
11045       if (first.useColors) {
11046         SendToProgram("white\n", &first); /*gnu kludge*/
11047       }
11048       SendTimeRemaining(&first, FALSE);
11049     }
11050     if (first.useColors) {
11051       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11052     }
11053     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11054     SetMachineThinkingEnables();
11055     first.maybeThinking = TRUE;
11056     StartClocks();
11057
11058     if (appData.autoFlipView && flipView) {
11059       flipView = !flipView;
11060       DrawPosition(FALSE, NULL);
11061       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11062     }
11063     if(bookHit) { // [HGM] book: simulate book reply
11064         static char bookMove[MSG_SIZ]; // a bit generous?
11065
11066         programStats.nodes = programStats.depth = programStats.time = 
11067         programStats.score = programStats.got_only_move = 0;
11068         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11069
11070         strcpy(bookMove, "move ");
11071         strcat(bookMove, bookHit);
11072         HandleMachineMove(bookMove, &first);
11073     }
11074 }
11075
11076
11077 void
11078 DisplayTwoMachinesTitle()
11079 {
11080     char buf[MSG_SIZ];
11081     if (appData.matchGames > 0) {
11082         if (first.twoMachinesColor[0] == 'w') {
11083             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11084                     gameInfo.white, gameInfo.black,
11085                     first.matchWins, second.matchWins,
11086                     matchGame - 1 - (first.matchWins + second.matchWins));
11087         } else {
11088             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11089                     gameInfo.white, gameInfo.black,
11090                     second.matchWins, first.matchWins,
11091                     matchGame - 1 - (first.matchWins + second.matchWins));
11092         }
11093     } else {
11094         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11095     }
11096     DisplayTitle(buf);
11097 }
11098
11099 void
11100 TwoMachinesEvent P((void))
11101 {
11102     int i;
11103     char buf[MSG_SIZ];
11104     ChessProgramState *onmove;
11105     char *bookHit = NULL;
11106     
11107     if (appData.noChessProgram) return;
11108
11109     switch (gameMode) {
11110       case TwoMachinesPlay:
11111         return;
11112       case MachinePlaysWhite:
11113       case MachinePlaysBlack:
11114         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11115             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11116             return;
11117         }
11118         /* fall through */
11119       case BeginningOfGame:
11120       case PlayFromGameFile:
11121       case EndOfGame:
11122         EditGameEvent();
11123         if (gameMode != EditGame) return;
11124         break;
11125       case EditPosition:
11126         EditPositionDone(TRUE);
11127         break;
11128       case AnalyzeMode:
11129       case AnalyzeFile:
11130         ExitAnalyzeMode();
11131         break;
11132       case EditGame:
11133       default:
11134         break;
11135     }
11136
11137 //    forwardMostMove = currentMove;
11138     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11139     ResurrectChessProgram();    /* in case first program isn't running */
11140
11141     if (second.pr == NULL) {
11142         StartChessProgram(&second);
11143         if (second.protocolVersion == 1) {
11144           TwoMachinesEventIfReady();
11145         } else {
11146           /* kludge: allow timeout for initial "feature" command */
11147           FreezeUI();
11148           DisplayMessage("", _("Starting second chess program"));
11149           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11150         }
11151         return;
11152     }
11153     DisplayMessage("", "");
11154     InitChessProgram(&second, FALSE);
11155     SendToProgram("force\n", &second);
11156     if (startedFromSetupPosition) {
11157         SendBoard(&second, backwardMostMove);
11158     if (appData.debugMode) {
11159         fprintf(debugFP, "Two Machines\n");
11160     }
11161     }
11162     for (i = backwardMostMove; i < forwardMostMove; i++) {
11163         SendMoveToProgram(i, &second);
11164     }
11165
11166     gameMode = TwoMachinesPlay;
11167     pausing = FALSE;
11168     ModeHighlight();
11169     SetGameInfo();
11170     DisplayTwoMachinesTitle();
11171     firstMove = TRUE;
11172     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11173         onmove = &first;
11174     } else {
11175         onmove = &second;
11176     }
11177
11178     SendToProgram(first.computerString, &first);
11179     if (first.sendName) {
11180       sprintf(buf, "name %s\n", second.tidy);
11181       SendToProgram(buf, &first);
11182     }
11183     SendToProgram(second.computerString, &second);
11184     if (second.sendName) {
11185       sprintf(buf, "name %s\n", first.tidy);
11186       SendToProgram(buf, &second);
11187     }
11188
11189     ResetClocks();
11190     if (!first.sendTime || !second.sendTime) {
11191         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11192         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11193     }
11194     if (onmove->sendTime) {
11195       if (onmove->useColors) {
11196         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11197       }
11198       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11199     }
11200     if (onmove->useColors) {
11201       SendToProgram(onmove->twoMachinesColor, onmove);
11202     }
11203     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11204 //    SendToProgram("go\n", onmove);
11205     onmove->maybeThinking = TRUE;
11206     SetMachineThinkingEnables();
11207
11208     StartClocks();
11209
11210     if(bookHit) { // [HGM] book: simulate book reply
11211         static char bookMove[MSG_SIZ]; // a bit generous?
11212
11213         programStats.nodes = programStats.depth = programStats.time = 
11214         programStats.score = programStats.got_only_move = 0;
11215         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11216
11217         strcpy(bookMove, "move ");
11218         strcat(bookMove, bookHit);
11219         savedMessage = bookMove; // args for deferred call
11220         savedState = onmove;
11221         ScheduleDelayedEvent(DeferredBookMove, 1);
11222     }
11223 }
11224
11225 void
11226 TrainingEvent()
11227 {
11228     if (gameMode == Training) {
11229       SetTrainingModeOff();
11230       gameMode = PlayFromGameFile;
11231       DisplayMessage("", _("Training mode off"));
11232     } else {
11233       gameMode = Training;
11234       animateTraining = appData.animate;
11235
11236       /* make sure we are not already at the end of the game */
11237       if (currentMove < forwardMostMove) {
11238         SetTrainingModeOn();
11239         DisplayMessage("", _("Training mode on"));
11240       } else {
11241         gameMode = PlayFromGameFile;
11242         DisplayError(_("Already at end of game"), 0);
11243       }
11244     }
11245     ModeHighlight();
11246 }
11247
11248 void
11249 IcsClientEvent()
11250 {
11251     if (!appData.icsActive) return;
11252     switch (gameMode) {
11253       case IcsPlayingWhite:
11254       case IcsPlayingBlack:
11255       case IcsObserving:
11256       case IcsIdle:
11257       case BeginningOfGame:
11258       case IcsExamining:
11259         return;
11260
11261       case EditGame:
11262         break;
11263
11264       case EditPosition:
11265         EditPositionDone(TRUE);
11266         break;
11267
11268       case AnalyzeMode:
11269       case AnalyzeFile:
11270         ExitAnalyzeMode();
11271         break;
11272         
11273       default:
11274         EditGameEvent();
11275         break;
11276     }
11277
11278     gameMode = IcsIdle;
11279     ModeHighlight();
11280     return;
11281 }
11282
11283
11284 void
11285 EditGameEvent()
11286 {
11287     int i;
11288
11289     switch (gameMode) {
11290       case Training:
11291         SetTrainingModeOff();
11292         break;
11293       case MachinePlaysWhite:
11294       case MachinePlaysBlack:
11295       case BeginningOfGame:
11296         SendToProgram("force\n", &first);
11297         SetUserThinkingEnables();
11298         break;
11299       case PlayFromGameFile:
11300         (void) StopLoadGameTimer();
11301         if (gameFileFP != NULL) {
11302             gameFileFP = NULL;
11303         }
11304         break;
11305       case EditPosition:
11306         EditPositionDone(TRUE);
11307         break;
11308       case AnalyzeMode:
11309       case AnalyzeFile:
11310         ExitAnalyzeMode();
11311         SendToProgram("force\n", &first);
11312         break;
11313       case TwoMachinesPlay:
11314         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11315         ResurrectChessProgram();
11316         SetUserThinkingEnables();
11317         break;
11318       case EndOfGame:
11319         ResurrectChessProgram();
11320         break;
11321       case IcsPlayingBlack:
11322       case IcsPlayingWhite:
11323         DisplayError(_("Warning: You are still playing a game"), 0);
11324         break;
11325       case IcsObserving:
11326         DisplayError(_("Warning: You are still observing a game"), 0);
11327         break;
11328       case IcsExamining:
11329         DisplayError(_("Warning: You are still examining a game"), 0);
11330         break;
11331       case IcsIdle:
11332         break;
11333       case EditGame:
11334       default:
11335         return;
11336     }
11337     
11338     pausing = FALSE;
11339     StopClocks();
11340     first.offeredDraw = second.offeredDraw = 0;
11341
11342     if (gameMode == PlayFromGameFile) {
11343         whiteTimeRemaining = timeRemaining[0][currentMove];
11344         blackTimeRemaining = timeRemaining[1][currentMove];
11345         DisplayTitle("");
11346     }
11347
11348     if (gameMode == MachinePlaysWhite ||
11349         gameMode == MachinePlaysBlack ||
11350         gameMode == TwoMachinesPlay ||
11351         gameMode == EndOfGame) {
11352         i = forwardMostMove;
11353         while (i > currentMove) {
11354             SendToProgram("undo\n", &first);
11355             i--;
11356         }
11357         whiteTimeRemaining = timeRemaining[0][currentMove];
11358         blackTimeRemaining = timeRemaining[1][currentMove];
11359         DisplayBothClocks();
11360         if (whiteFlag || blackFlag) {
11361             whiteFlag = blackFlag = 0;
11362         }
11363         DisplayTitle("");
11364     }           
11365     
11366     gameMode = EditGame;
11367     ModeHighlight();
11368     SetGameInfo();
11369 }
11370
11371
11372 void
11373 EditPositionEvent()
11374 {
11375     if (gameMode == EditPosition) {
11376         EditGameEvent();
11377         return;
11378     }
11379     
11380     EditGameEvent();
11381     if (gameMode != EditGame) return;
11382     
11383     gameMode = EditPosition;
11384     ModeHighlight();
11385     SetGameInfo();
11386     if (currentMove > 0)
11387       CopyBoard(boards[0], boards[currentMove]);
11388     
11389     blackPlaysFirst = !WhiteOnMove(currentMove);
11390     ResetClocks();
11391     currentMove = forwardMostMove = backwardMostMove = 0;
11392     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11393     DisplayMove(-1);
11394 }
11395
11396 void
11397 ExitAnalyzeMode()
11398 {
11399     /* [DM] icsEngineAnalyze - possible call from other functions */
11400     if (appData.icsEngineAnalyze) {
11401         appData.icsEngineAnalyze = FALSE;
11402
11403         DisplayMessage("",_("Close ICS engine analyze..."));
11404     }
11405     if (first.analysisSupport && first.analyzing) {
11406       SendToProgram("exit\n", &first);
11407       first.analyzing = FALSE;
11408     }
11409     thinkOutput[0] = NULLCHAR;
11410 }
11411
11412 void
11413 EditPositionDone(Boolean fakeRights)
11414 {
11415     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11416
11417     startedFromSetupPosition = TRUE;
11418     InitChessProgram(&first, FALSE);
11419     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11420       boards[0][EP_STATUS] = EP_NONE;
11421       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11422     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11423         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11424         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11425       } else boards[0][CASTLING][2] = NoRights;
11426     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11427         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11428         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11429       } else boards[0][CASTLING][5] = NoRights;
11430     }
11431     SendToProgram("force\n", &first);
11432     if (blackPlaysFirst) {
11433         strcpy(moveList[0], "");
11434         strcpy(parseList[0], "");
11435         currentMove = forwardMostMove = backwardMostMove = 1;
11436         CopyBoard(boards[1], boards[0]);
11437     } else {
11438         currentMove = forwardMostMove = backwardMostMove = 0;
11439     }
11440     SendBoard(&first, forwardMostMove);
11441     if (appData.debugMode) {
11442         fprintf(debugFP, "EditPosDone\n");
11443     }
11444     DisplayTitle("");
11445     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11446     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11447     gameMode = EditGame;
11448     ModeHighlight();
11449     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11450     ClearHighlights(); /* [AS] */
11451 }
11452
11453 /* Pause for `ms' milliseconds */
11454 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11455 void
11456 TimeDelay(ms)
11457      long ms;
11458 {
11459     TimeMark m1, m2;
11460
11461     GetTimeMark(&m1);
11462     do {
11463         GetTimeMark(&m2);
11464     } while (SubtractTimeMarks(&m2, &m1) < ms);
11465 }
11466
11467 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11468 void
11469 SendMultiLineToICS(buf)
11470      char *buf;
11471 {
11472     char temp[MSG_SIZ+1], *p;
11473     int len;
11474
11475     len = strlen(buf);
11476     if (len > MSG_SIZ)
11477       len = MSG_SIZ;
11478   
11479     strncpy(temp, buf, len);
11480     temp[len] = 0;
11481
11482     p = temp;
11483     while (*p) {
11484         if (*p == '\n' || *p == '\r')
11485           *p = ' ';
11486         ++p;
11487     }
11488
11489     strcat(temp, "\n");
11490     SendToICS(temp);
11491     SendToPlayer(temp, strlen(temp));
11492 }
11493
11494 void
11495 SetWhiteToPlayEvent()
11496 {
11497     if (gameMode == EditPosition) {
11498         blackPlaysFirst = FALSE;
11499         DisplayBothClocks();    /* works because currentMove is 0 */
11500     } else if (gameMode == IcsExamining) {
11501         SendToICS(ics_prefix);
11502         SendToICS("tomove white\n");
11503     }
11504 }
11505
11506 void
11507 SetBlackToPlayEvent()
11508 {
11509     if (gameMode == EditPosition) {
11510         blackPlaysFirst = TRUE;
11511         currentMove = 1;        /* kludge */
11512         DisplayBothClocks();
11513         currentMove = 0;
11514     } else if (gameMode == IcsExamining) {
11515         SendToICS(ics_prefix);
11516         SendToICS("tomove black\n");
11517     }
11518 }
11519
11520 void
11521 EditPositionMenuEvent(selection, x, y)
11522      ChessSquare selection;
11523      int x, y;
11524 {
11525     char buf[MSG_SIZ];
11526     ChessSquare piece = boards[0][y][x];
11527
11528     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11529
11530     switch (selection) {
11531       case ClearBoard:
11532         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11533             SendToICS(ics_prefix);
11534             SendToICS("bsetup clear\n");
11535         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11536             SendToICS(ics_prefix);
11537             SendToICS("clearboard\n");
11538         } else {
11539             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11540                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11541                 for (y = 0; y < BOARD_HEIGHT; y++) {
11542                     if (gameMode == IcsExamining) {
11543                         if (boards[currentMove][y][x] != EmptySquare) {
11544                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11545                                     AAA + x, ONE + y);
11546                             SendToICS(buf);
11547                         }
11548                     } else {
11549                         boards[0][y][x] = p;
11550                     }
11551                 }
11552             }
11553         }
11554         if (gameMode == EditPosition) {
11555             DrawPosition(FALSE, boards[0]);
11556         }
11557         break;
11558
11559       case WhitePlay:
11560         SetWhiteToPlayEvent();
11561         break;
11562
11563       case BlackPlay:
11564         SetBlackToPlayEvent();
11565         break;
11566
11567       case EmptySquare:
11568         if (gameMode == IcsExamining) {
11569             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11570             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11571             SendToICS(buf);
11572         } else {
11573             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11574                 if(x == BOARD_LEFT-2) {
11575                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11576                     boards[0][y][1] = 0;
11577                 } else
11578                 if(x == BOARD_RGHT+1) {
11579                     if(y >= gameInfo.holdingsSize) break;
11580                     boards[0][y][BOARD_WIDTH-2] = 0;
11581                 } else break;
11582             }
11583             boards[0][y][x] = EmptySquare;
11584             DrawPosition(FALSE, boards[0]);
11585         }
11586         break;
11587
11588       case PromotePiece:
11589         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11590            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11591             selection = (ChessSquare) (PROMOTED piece);
11592         } else if(piece == EmptySquare) selection = WhiteSilver;
11593         else selection = (ChessSquare)((int)piece - 1);
11594         goto defaultlabel;
11595
11596       case DemotePiece:
11597         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11598            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11599             selection = (ChessSquare) (DEMOTED piece);
11600         } else if(piece == EmptySquare) selection = BlackSilver;
11601         else selection = (ChessSquare)((int)piece + 1);       
11602         goto defaultlabel;
11603
11604       case WhiteQueen:
11605       case BlackQueen:
11606         if(gameInfo.variant == VariantShatranj ||
11607            gameInfo.variant == VariantXiangqi  ||
11608            gameInfo.variant == VariantCourier  ||
11609            gameInfo.variant == VariantMakruk     )
11610             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11611         goto defaultlabel;
11612
11613       case WhiteKing:
11614       case BlackKing:
11615         if(gameInfo.variant == VariantXiangqi)
11616             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11617         if(gameInfo.variant == VariantKnightmate)
11618             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11619       default:
11620         defaultlabel:
11621         if (gameMode == IcsExamining) {
11622             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11623             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11624                     PieceToChar(selection), AAA + x, ONE + y);
11625             SendToICS(buf);
11626         } else {
11627             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11628                 int n;
11629                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11630                     n = PieceToNumber(selection - BlackPawn);
11631                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11632                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11633                     boards[0][BOARD_HEIGHT-1-n][1]++;
11634                 } else
11635                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11636                     n = PieceToNumber(selection);
11637                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11638                     boards[0][n][BOARD_WIDTH-1] = selection;
11639                     boards[0][n][BOARD_WIDTH-2]++;
11640                 }
11641             } else
11642             boards[0][y][x] = selection;
11643             DrawPosition(TRUE, boards[0]);
11644         }
11645         break;
11646     }
11647 }
11648
11649
11650 void
11651 DropMenuEvent(selection, x, y)
11652      ChessSquare selection;
11653      int x, y;
11654 {
11655     ChessMove moveType;
11656
11657     switch (gameMode) {
11658       case IcsPlayingWhite:
11659       case MachinePlaysBlack:
11660         if (!WhiteOnMove(currentMove)) {
11661             DisplayMoveError(_("It is Black's turn"));
11662             return;
11663         }
11664         moveType = WhiteDrop;
11665         break;
11666       case IcsPlayingBlack:
11667       case MachinePlaysWhite:
11668         if (WhiteOnMove(currentMove)) {
11669             DisplayMoveError(_("It is White's turn"));
11670             return;
11671         }
11672         moveType = BlackDrop;
11673         break;
11674       case EditGame:
11675         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11676         break;
11677       default:
11678         return;
11679     }
11680
11681     if (moveType == BlackDrop && selection < BlackPawn) {
11682       selection = (ChessSquare) ((int) selection
11683                                  + (int) BlackPawn - (int) WhitePawn);
11684     }
11685     if (boards[currentMove][y][x] != EmptySquare) {
11686         DisplayMoveError(_("That square is occupied"));
11687         return;
11688     }
11689
11690     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11691 }
11692
11693 void
11694 AcceptEvent()
11695 {
11696     /* Accept a pending offer of any kind from opponent */
11697     
11698     if (appData.icsActive) {
11699         SendToICS(ics_prefix);
11700         SendToICS("accept\n");
11701     } else if (cmailMsgLoaded) {
11702         if (currentMove == cmailOldMove &&
11703             commentList[cmailOldMove] != NULL &&
11704             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11705                    "Black offers a draw" : "White offers a draw")) {
11706             TruncateGame();
11707             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11708             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11709         } else {
11710             DisplayError(_("There is no pending offer on this move"), 0);
11711             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11712         }
11713     } else {
11714         /* Not used for offers from chess program */
11715     }
11716 }
11717
11718 void
11719 DeclineEvent()
11720 {
11721     /* Decline a pending offer of any kind from opponent */
11722     
11723     if (appData.icsActive) {
11724         SendToICS(ics_prefix);
11725         SendToICS("decline\n");
11726     } else if (cmailMsgLoaded) {
11727         if (currentMove == cmailOldMove &&
11728             commentList[cmailOldMove] != NULL &&
11729             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11730                    "Black offers a draw" : "White offers a draw")) {
11731 #ifdef NOTDEF
11732             AppendComment(cmailOldMove, "Draw declined", TRUE);
11733             DisplayComment(cmailOldMove - 1, "Draw declined");
11734 #endif /*NOTDEF*/
11735         } else {
11736             DisplayError(_("There is no pending offer on this move"), 0);
11737         }
11738     } else {
11739         /* Not used for offers from chess program */
11740     }
11741 }
11742
11743 void
11744 RematchEvent()
11745 {
11746     /* Issue ICS rematch command */
11747     if (appData.icsActive) {
11748         SendToICS(ics_prefix);
11749         SendToICS("rematch\n");
11750     }
11751 }
11752
11753 void
11754 CallFlagEvent()
11755 {
11756     /* Call your opponent's flag (claim a win on time) */
11757     if (appData.icsActive) {
11758         SendToICS(ics_prefix);
11759         SendToICS("flag\n");
11760     } else {
11761         switch (gameMode) {
11762           default:
11763             return;
11764           case MachinePlaysWhite:
11765             if (whiteFlag) {
11766                 if (blackFlag)
11767                   GameEnds(GameIsDrawn, "Both players ran out of time",
11768                            GE_PLAYER);
11769                 else
11770                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11771             } else {
11772                 DisplayError(_("Your opponent is not out of time"), 0);
11773             }
11774             break;
11775           case MachinePlaysBlack:
11776             if (blackFlag) {
11777                 if (whiteFlag)
11778                   GameEnds(GameIsDrawn, "Both players ran out of time",
11779                            GE_PLAYER);
11780                 else
11781                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11782             } else {
11783                 DisplayError(_("Your opponent is not out of time"), 0);
11784             }
11785             break;
11786         }
11787     }
11788 }
11789
11790 void
11791 DrawEvent()
11792 {
11793     /* Offer draw or accept pending draw offer from opponent */
11794     
11795     if (appData.icsActive) {
11796         /* Note: tournament rules require draw offers to be
11797            made after you make your move but before you punch
11798            your clock.  Currently ICS doesn't let you do that;
11799            instead, you immediately punch your clock after making
11800            a move, but you can offer a draw at any time. */
11801         
11802         SendToICS(ics_prefix);
11803         SendToICS("draw\n");
11804         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
11805     } else if (cmailMsgLoaded) {
11806         if (currentMove == cmailOldMove &&
11807             commentList[cmailOldMove] != NULL &&
11808             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11809                    "Black offers a draw" : "White offers a draw")) {
11810             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11811             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11812         } else if (currentMove == cmailOldMove + 1) {
11813             char *offer = WhiteOnMove(cmailOldMove) ?
11814               "White offers a draw" : "Black offers a draw";
11815             AppendComment(currentMove, offer, TRUE);
11816             DisplayComment(currentMove - 1, offer);
11817             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11818         } else {
11819             DisplayError(_("You must make your move before offering a draw"), 0);
11820             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11821         }
11822     } else if (first.offeredDraw) {
11823         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11824     } else {
11825         if (first.sendDrawOffers) {
11826             SendToProgram("draw\n", &first);
11827             userOfferedDraw = TRUE;
11828         }
11829     }
11830 }
11831
11832 void
11833 AdjournEvent()
11834 {
11835     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11836     
11837     if (appData.icsActive) {
11838         SendToICS(ics_prefix);
11839         SendToICS("adjourn\n");
11840     } else {
11841         /* Currently GNU Chess doesn't offer or accept Adjourns */
11842     }
11843 }
11844
11845
11846 void
11847 AbortEvent()
11848 {
11849     /* Offer Abort or accept pending Abort offer from opponent */
11850     
11851     if (appData.icsActive) {
11852         SendToICS(ics_prefix);
11853         SendToICS("abort\n");
11854     } else {
11855         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11856     }
11857 }
11858
11859 void
11860 ResignEvent()
11861 {
11862     /* Resign.  You can do this even if it's not your turn. */
11863     
11864     if (appData.icsActive) {
11865         SendToICS(ics_prefix);
11866         SendToICS("resign\n");
11867     } else {
11868         switch (gameMode) {
11869           case MachinePlaysWhite:
11870             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11871             break;
11872           case MachinePlaysBlack:
11873             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11874             break;
11875           case EditGame:
11876             if (cmailMsgLoaded) {
11877                 TruncateGame();
11878                 if (WhiteOnMove(cmailOldMove)) {
11879                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11880                 } else {
11881                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11882                 }
11883                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11884             }
11885             break;
11886           default:
11887             break;
11888         }
11889     }
11890 }
11891
11892
11893 void
11894 StopObservingEvent()
11895 {
11896     /* Stop observing current games */
11897     SendToICS(ics_prefix);
11898     SendToICS("unobserve\n");
11899 }
11900
11901 void
11902 StopExaminingEvent()
11903 {
11904     /* Stop observing current game */
11905     SendToICS(ics_prefix);
11906     SendToICS("unexamine\n");
11907 }
11908
11909 void
11910 ForwardInner(target)
11911      int target;
11912 {
11913     int limit;
11914
11915     if (appData.debugMode)
11916         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11917                 target, currentMove, forwardMostMove);
11918
11919     if (gameMode == EditPosition)
11920       return;
11921
11922     if (gameMode == PlayFromGameFile && !pausing)
11923       PauseEvent();
11924     
11925     if (gameMode == IcsExamining && pausing)
11926       limit = pauseExamForwardMostMove;
11927     else
11928       limit = forwardMostMove;
11929     
11930     if (target > limit) target = limit;
11931
11932     if (target > 0 && moveList[target - 1][0]) {
11933         int fromX, fromY, toX, toY;
11934         toX = moveList[target - 1][2] - AAA;
11935         toY = moveList[target - 1][3] - ONE;
11936         if (moveList[target - 1][1] == '@') {
11937             if (appData.highlightLastMove) {
11938                 SetHighlights(-1, -1, toX, toY);
11939             }
11940         } else {
11941             fromX = moveList[target - 1][0] - AAA;
11942             fromY = moveList[target - 1][1] - ONE;
11943             if (target == currentMove + 1) {
11944                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11945             }
11946             if (appData.highlightLastMove) {
11947                 SetHighlights(fromX, fromY, toX, toY);
11948             }
11949         }
11950     }
11951     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11952         gameMode == Training || gameMode == PlayFromGameFile || 
11953         gameMode == AnalyzeFile) {
11954         while (currentMove < target) {
11955             SendMoveToProgram(currentMove++, &first);
11956         }
11957     } else {
11958         currentMove = target;
11959     }
11960     
11961     if (gameMode == EditGame || gameMode == EndOfGame) {
11962         whiteTimeRemaining = timeRemaining[0][currentMove];
11963         blackTimeRemaining = timeRemaining[1][currentMove];
11964     }
11965     DisplayBothClocks();
11966     DisplayMove(currentMove - 1);
11967     DrawPosition(FALSE, boards[currentMove]);
11968     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11969     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11970         DisplayComment(currentMove - 1, commentList[currentMove]);
11971     }
11972 }
11973
11974
11975 void
11976 ForwardEvent()
11977 {
11978     if (gameMode == IcsExamining && !pausing) {
11979         SendToICS(ics_prefix);
11980         SendToICS("forward\n");
11981     } else {
11982         ForwardInner(currentMove + 1);
11983     }
11984 }
11985
11986 void
11987 ToEndEvent()
11988 {
11989     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11990         /* to optimze, we temporarily turn off analysis mode while we feed
11991          * the remaining moves to the engine. Otherwise we get analysis output
11992          * after each move.
11993          */ 
11994         if (first.analysisSupport) {
11995           SendToProgram("exit\nforce\n", &first);
11996           first.analyzing = FALSE;
11997         }
11998     }
11999         
12000     if (gameMode == IcsExamining && !pausing) {
12001         SendToICS(ics_prefix);
12002         SendToICS("forward 999999\n");
12003     } else {
12004         ForwardInner(forwardMostMove);
12005     }
12006
12007     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12008         /* we have fed all the moves, so reactivate analysis mode */
12009         SendToProgram("analyze\n", &first);
12010         first.analyzing = TRUE;
12011         /*first.maybeThinking = TRUE;*/
12012         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12013     }
12014 }
12015
12016 void
12017 BackwardInner(target)
12018      int target;
12019 {
12020     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12021
12022     if (appData.debugMode)
12023         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12024                 target, currentMove, forwardMostMove);
12025
12026     if (gameMode == EditPosition) return;
12027     if (currentMove <= backwardMostMove) {
12028         ClearHighlights();
12029         DrawPosition(full_redraw, boards[currentMove]);
12030         return;
12031     }
12032     if (gameMode == PlayFromGameFile && !pausing)
12033       PauseEvent();
12034     
12035     if (moveList[target][0]) {
12036         int fromX, fromY, toX, toY;
12037         toX = moveList[target][2] - AAA;
12038         toY = moveList[target][3] - ONE;
12039         if (moveList[target][1] == '@') {
12040             if (appData.highlightLastMove) {
12041                 SetHighlights(-1, -1, toX, toY);
12042             }
12043         } else {
12044             fromX = moveList[target][0] - AAA;
12045             fromY = moveList[target][1] - ONE;
12046             if (target == currentMove - 1) {
12047                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12048             }
12049             if (appData.highlightLastMove) {
12050                 SetHighlights(fromX, fromY, toX, toY);
12051             }
12052         }
12053     }
12054     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12055         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12056         while (currentMove > target) {
12057             SendToProgram("undo\n", &first);
12058             currentMove--;
12059         }
12060     } else {
12061         currentMove = target;
12062     }
12063     
12064     if (gameMode == EditGame || gameMode == EndOfGame) {
12065         whiteTimeRemaining = timeRemaining[0][currentMove];
12066         blackTimeRemaining = timeRemaining[1][currentMove];
12067     }
12068     DisplayBothClocks();
12069     DisplayMove(currentMove - 1);
12070     DrawPosition(full_redraw, boards[currentMove]);
12071     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12072     // [HGM] PV info: routine tests if comment empty
12073     DisplayComment(currentMove - 1, commentList[currentMove]);
12074 }
12075
12076 void
12077 BackwardEvent()
12078 {
12079     if (gameMode == IcsExamining && !pausing) {
12080         SendToICS(ics_prefix);
12081         SendToICS("backward\n");
12082     } else {
12083         BackwardInner(currentMove - 1);
12084     }
12085 }
12086
12087 void
12088 ToStartEvent()
12089 {
12090     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12091         /* to optimize, we temporarily turn off analysis mode while we undo
12092          * all the moves. Otherwise we get analysis output after each undo.
12093          */ 
12094         if (first.analysisSupport) {
12095           SendToProgram("exit\nforce\n", &first);
12096           first.analyzing = FALSE;
12097         }
12098     }
12099
12100     if (gameMode == IcsExamining && !pausing) {
12101         SendToICS(ics_prefix);
12102         SendToICS("backward 999999\n");
12103     } else {
12104         BackwardInner(backwardMostMove);
12105     }
12106
12107     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12108         /* we have fed all the moves, so reactivate analysis mode */
12109         SendToProgram("analyze\n", &first);
12110         first.analyzing = TRUE;
12111         /*first.maybeThinking = TRUE;*/
12112         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12113     }
12114 }
12115
12116 void
12117 ToNrEvent(int to)
12118 {
12119   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12120   if (to >= forwardMostMove) to = forwardMostMove;
12121   if (to <= backwardMostMove) to = backwardMostMove;
12122   if (to < currentMove) {
12123     BackwardInner(to);
12124   } else {
12125     ForwardInner(to);
12126   }
12127 }
12128
12129 void
12130 RevertEvent()
12131 {
12132     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12133         return;
12134     }
12135     if (gameMode != IcsExamining) {
12136         DisplayError(_("You are not examining a game"), 0);
12137         return;
12138     }
12139     if (pausing) {
12140         DisplayError(_("You can't revert while pausing"), 0);
12141         return;
12142     }
12143     SendToICS(ics_prefix);
12144     SendToICS("revert\n");
12145 }
12146
12147 void
12148 RetractMoveEvent()
12149 {
12150     switch (gameMode) {
12151       case MachinePlaysWhite:
12152       case MachinePlaysBlack:
12153         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12154             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12155             return;
12156         }
12157         if (forwardMostMove < 2) return;
12158         currentMove = forwardMostMove = forwardMostMove - 2;
12159         whiteTimeRemaining = timeRemaining[0][currentMove];
12160         blackTimeRemaining = timeRemaining[1][currentMove];
12161         DisplayBothClocks();
12162         DisplayMove(currentMove - 1);
12163         ClearHighlights();/*!! could figure this out*/
12164         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12165         SendToProgram("remove\n", &first);
12166         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12167         break;
12168
12169       case BeginningOfGame:
12170       default:
12171         break;
12172
12173       case IcsPlayingWhite:
12174       case IcsPlayingBlack:
12175         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12176             SendToICS(ics_prefix);
12177             SendToICS("takeback 2\n");
12178         } else {
12179             SendToICS(ics_prefix);
12180             SendToICS("takeback 1\n");
12181         }
12182         break;
12183     }
12184 }
12185
12186 void
12187 MoveNowEvent()
12188 {
12189     ChessProgramState *cps;
12190
12191     switch (gameMode) {
12192       case MachinePlaysWhite:
12193         if (!WhiteOnMove(forwardMostMove)) {
12194             DisplayError(_("It is your turn"), 0);
12195             return;
12196         }
12197         cps = &first;
12198         break;
12199       case MachinePlaysBlack:
12200         if (WhiteOnMove(forwardMostMove)) {
12201             DisplayError(_("It is your turn"), 0);
12202             return;
12203         }
12204         cps = &first;
12205         break;
12206       case TwoMachinesPlay:
12207         if (WhiteOnMove(forwardMostMove) ==
12208             (first.twoMachinesColor[0] == 'w')) {
12209             cps = &first;
12210         } else {
12211             cps = &second;
12212         }
12213         break;
12214       case BeginningOfGame:
12215       default:
12216         return;
12217     }
12218     SendToProgram("?\n", cps);
12219 }
12220
12221 void
12222 TruncateGameEvent()
12223 {
12224     EditGameEvent();
12225     if (gameMode != EditGame) return;
12226     TruncateGame();
12227 }
12228
12229 void
12230 TruncateGame()
12231 {
12232     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12233     if (forwardMostMove > currentMove) {
12234         if (gameInfo.resultDetails != NULL) {
12235             free(gameInfo.resultDetails);
12236             gameInfo.resultDetails = NULL;
12237             gameInfo.result = GameUnfinished;
12238         }
12239         forwardMostMove = currentMove;
12240         HistorySet(parseList, backwardMostMove, forwardMostMove,
12241                    currentMove-1);
12242     }
12243 }
12244
12245 void
12246 HintEvent()
12247 {
12248     if (appData.noChessProgram) return;
12249     switch (gameMode) {
12250       case MachinePlaysWhite:
12251         if (WhiteOnMove(forwardMostMove)) {
12252             DisplayError(_("Wait until your turn"), 0);
12253             return;
12254         }
12255         break;
12256       case BeginningOfGame:
12257       case MachinePlaysBlack:
12258         if (!WhiteOnMove(forwardMostMove)) {
12259             DisplayError(_("Wait until your turn"), 0);
12260             return;
12261         }
12262         break;
12263       default:
12264         DisplayError(_("No hint available"), 0);
12265         return;
12266     }
12267     SendToProgram("hint\n", &first);
12268     hintRequested = TRUE;
12269 }
12270
12271 void
12272 BookEvent()
12273 {
12274     if (appData.noChessProgram) return;
12275     switch (gameMode) {
12276       case MachinePlaysWhite:
12277         if (WhiteOnMove(forwardMostMove)) {
12278             DisplayError(_("Wait until your turn"), 0);
12279             return;
12280         }
12281         break;
12282       case BeginningOfGame:
12283       case MachinePlaysBlack:
12284         if (!WhiteOnMove(forwardMostMove)) {
12285             DisplayError(_("Wait until your turn"), 0);
12286             return;
12287         }
12288         break;
12289       case EditPosition:
12290         EditPositionDone(TRUE);
12291         break;
12292       case TwoMachinesPlay:
12293         return;
12294       default:
12295         break;
12296     }
12297     SendToProgram("bk\n", &first);
12298     bookOutput[0] = NULLCHAR;
12299     bookRequested = TRUE;
12300 }
12301
12302 void
12303 AboutGameEvent()
12304 {
12305     char *tags = PGNTags(&gameInfo);
12306     TagsPopUp(tags, CmailMsg());
12307     free(tags);
12308 }
12309
12310 /* end button procedures */
12311
12312 void
12313 PrintPosition(fp, move)
12314      FILE *fp;
12315      int move;
12316 {
12317     int i, j;
12318     
12319     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12320         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12321             char c = PieceToChar(boards[move][i][j]);
12322             fputc(c == 'x' ? '.' : c, fp);
12323             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12324         }
12325     }
12326     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12327       fprintf(fp, "white to play\n");
12328     else
12329       fprintf(fp, "black to play\n");
12330 }
12331
12332 void
12333 PrintOpponents(fp)
12334      FILE *fp;
12335 {
12336     if (gameInfo.white != NULL) {
12337         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12338     } else {
12339         fprintf(fp, "\n");
12340     }
12341 }
12342
12343 /* Find last component of program's own name, using some heuristics */
12344 void
12345 TidyProgramName(prog, host, buf)
12346      char *prog, *host, buf[MSG_SIZ];
12347 {
12348     char *p, *q;
12349     int local = (strcmp(host, "localhost") == 0);
12350     while (!local && (p = strchr(prog, ';')) != NULL) {
12351         p++;
12352         while (*p == ' ') p++;
12353         prog = p;
12354     }
12355     if (*prog == '"' || *prog == '\'') {
12356         q = strchr(prog + 1, *prog);
12357     } else {
12358         q = strchr(prog, ' ');
12359     }
12360     if (q == NULL) q = prog + strlen(prog);
12361     p = q;
12362     while (p >= prog && *p != '/' && *p != '\\') p--;
12363     p++;
12364     if(p == prog && *p == '"') p++;
12365     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12366     memcpy(buf, p, q - p);
12367     buf[q - p] = NULLCHAR;
12368     if (!local) {
12369         strcat(buf, "@");
12370         strcat(buf, host);
12371     }
12372 }
12373
12374 char *
12375 TimeControlTagValue()
12376 {
12377     char buf[MSG_SIZ];
12378     if (!appData.clockMode) {
12379         strcpy(buf, "-");
12380     } else if (movesPerSession > 0) {
12381         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12382     } else if (timeIncrement == 0) {
12383         sprintf(buf, "%ld", timeControl/1000);
12384     } else {
12385         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12386     }
12387     return StrSave(buf);
12388 }
12389
12390 void
12391 SetGameInfo()
12392 {
12393     /* This routine is used only for certain modes */
12394     VariantClass v = gameInfo.variant;
12395     ChessMove r = GameUnfinished;
12396     char *p = NULL;
12397
12398     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12399         r = gameInfo.result; 
12400         p = gameInfo.resultDetails; 
12401         gameInfo.resultDetails = NULL;
12402     }
12403     ClearGameInfo(&gameInfo);
12404     gameInfo.variant = v;
12405
12406     switch (gameMode) {
12407       case MachinePlaysWhite:
12408         gameInfo.event = StrSave( appData.pgnEventHeader );
12409         gameInfo.site = StrSave(HostName());
12410         gameInfo.date = PGNDate();
12411         gameInfo.round = StrSave("-");
12412         gameInfo.white = StrSave(first.tidy);
12413         gameInfo.black = StrSave(UserName());
12414         gameInfo.timeControl = TimeControlTagValue();
12415         break;
12416
12417       case MachinePlaysBlack:
12418         gameInfo.event = StrSave( appData.pgnEventHeader );
12419         gameInfo.site = StrSave(HostName());
12420         gameInfo.date = PGNDate();
12421         gameInfo.round = StrSave("-");
12422         gameInfo.white = StrSave(UserName());
12423         gameInfo.black = StrSave(first.tidy);
12424         gameInfo.timeControl = TimeControlTagValue();
12425         break;
12426
12427       case TwoMachinesPlay:
12428         gameInfo.event = StrSave( appData.pgnEventHeader );
12429         gameInfo.site = StrSave(HostName());
12430         gameInfo.date = PGNDate();
12431         if (matchGame > 0) {
12432             char buf[MSG_SIZ];
12433             sprintf(buf, "%d", matchGame);
12434             gameInfo.round = StrSave(buf);
12435         } else {
12436             gameInfo.round = StrSave("-");
12437         }
12438         if (first.twoMachinesColor[0] == 'w') {
12439             gameInfo.white = StrSave(first.tidy);
12440             gameInfo.black = StrSave(second.tidy);
12441         } else {
12442             gameInfo.white = StrSave(second.tidy);
12443             gameInfo.black = StrSave(first.tidy);
12444         }
12445         gameInfo.timeControl = TimeControlTagValue();
12446         break;
12447
12448       case EditGame:
12449         gameInfo.event = StrSave("Edited game");
12450         gameInfo.site = StrSave(HostName());
12451         gameInfo.date = PGNDate();
12452         gameInfo.round = StrSave("-");
12453         gameInfo.white = StrSave("-");
12454         gameInfo.black = StrSave("-");
12455         gameInfo.result = r;
12456         gameInfo.resultDetails = p;
12457         break;
12458
12459       case EditPosition:
12460         gameInfo.event = StrSave("Edited position");
12461         gameInfo.site = StrSave(HostName());
12462         gameInfo.date = PGNDate();
12463         gameInfo.round = StrSave("-");
12464         gameInfo.white = StrSave("-");
12465         gameInfo.black = StrSave("-");
12466         break;
12467
12468       case IcsPlayingWhite:
12469       case IcsPlayingBlack:
12470       case IcsObserving:
12471       case IcsExamining:
12472         break;
12473
12474       case PlayFromGameFile:
12475         gameInfo.event = StrSave("Game from non-PGN file");
12476         gameInfo.site = StrSave(HostName());
12477         gameInfo.date = PGNDate();
12478         gameInfo.round = StrSave("-");
12479         gameInfo.white = StrSave("?");
12480         gameInfo.black = StrSave("?");
12481         break;
12482
12483       default:
12484         break;
12485     }
12486 }
12487
12488 void
12489 ReplaceComment(index, text)
12490      int index;
12491      char *text;
12492 {
12493     int len;
12494
12495     while (*text == '\n') text++;
12496     len = strlen(text);
12497     while (len > 0 && text[len - 1] == '\n') len--;
12498
12499     if (commentList[index] != NULL)
12500       free(commentList[index]);
12501
12502     if (len == 0) {
12503         commentList[index] = NULL;
12504         return;
12505     }
12506   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12507       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12508       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12509     commentList[index] = (char *) malloc(len + 2);
12510     strncpy(commentList[index], text, len);
12511     commentList[index][len] = '\n';
12512     commentList[index][len + 1] = NULLCHAR;
12513   } else { 
12514     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12515     char *p;
12516     commentList[index] = (char *) malloc(len + 6);
12517     strcpy(commentList[index], "{\n");
12518     strncpy(commentList[index]+2, text, len);
12519     commentList[index][len+2] = NULLCHAR;
12520     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12521     strcat(commentList[index], "\n}\n");
12522   }
12523 }
12524
12525 void
12526 CrushCRs(text)
12527      char *text;
12528 {
12529   char *p = text;
12530   char *q = text;
12531   char ch;
12532
12533   do {
12534     ch = *p++;
12535     if (ch == '\r') continue;
12536     *q++ = ch;
12537   } while (ch != '\0');
12538 }
12539
12540 void
12541 AppendComment(index, text, addBraces)
12542      int index;
12543      char *text;
12544      Boolean addBraces; // [HGM] braces: tells if we should add {}
12545 {
12546     int oldlen, len;
12547     char *old;
12548
12549 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12550     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12551
12552     CrushCRs(text);
12553     while (*text == '\n') text++;
12554     len = strlen(text);
12555     while (len > 0 && text[len - 1] == '\n') len--;
12556
12557     if (len == 0) return;
12558
12559     if (commentList[index] != NULL) {
12560         old = commentList[index];
12561         oldlen = strlen(old);
12562         while(commentList[index][oldlen-1] ==  '\n')
12563           commentList[index][--oldlen] = NULLCHAR;
12564         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12565         strcpy(commentList[index], old);
12566         free(old);
12567         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12568         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12569           if(addBraces) addBraces = FALSE; else { text++; len--; }
12570           while (*text == '\n') { text++; len--; }
12571           commentList[index][--oldlen] = NULLCHAR;
12572       }
12573         if(addBraces) strcat(commentList[index], "\n{\n");
12574         else          strcat(commentList[index], "\n");
12575         strcat(commentList[index], text);
12576         if(addBraces) strcat(commentList[index], "\n}\n");
12577         else          strcat(commentList[index], "\n");
12578     } else {
12579         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12580         if(addBraces)
12581              strcpy(commentList[index], "{\n");
12582         else commentList[index][0] = NULLCHAR;
12583         strcat(commentList[index], text);
12584         strcat(commentList[index], "\n");
12585         if(addBraces) strcat(commentList[index], "}\n");
12586     }
12587 }
12588
12589 static char * FindStr( char * text, char * sub_text )
12590 {
12591     char * result = strstr( text, sub_text );
12592
12593     if( result != NULL ) {
12594         result += strlen( sub_text );
12595     }
12596
12597     return result;
12598 }
12599
12600 /* [AS] Try to extract PV info from PGN comment */
12601 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12602 char *GetInfoFromComment( int index, char * text )
12603 {
12604     char * sep = text;
12605
12606     if( text != NULL && index > 0 ) {
12607         int score = 0;
12608         int depth = 0;
12609         int time = -1, sec = 0, deci;
12610         char * s_eval = FindStr( text, "[%eval " );
12611         char * s_emt = FindStr( text, "[%emt " );
12612
12613         if( s_eval != NULL || s_emt != NULL ) {
12614             /* New style */
12615             char delim;
12616
12617             if( s_eval != NULL ) {
12618                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12619                     return text;
12620                 }
12621
12622                 if( delim != ']' ) {
12623                     return text;
12624                 }
12625             }
12626
12627             if( s_emt != NULL ) {
12628             }
12629                 return text;
12630         }
12631         else {
12632             /* We expect something like: [+|-]nnn.nn/dd */
12633             int score_lo = 0;
12634
12635             if(*text != '{') return text; // [HGM] braces: must be normal comment
12636
12637             sep = strchr( text, '/' );
12638             if( sep == NULL || sep < (text+4) ) {
12639                 return text;
12640             }
12641
12642             time = -1; sec = -1; deci = -1;
12643             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12644                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12645                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12646                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12647                 return text;
12648             }
12649
12650             if( score_lo < 0 || score_lo >= 100 ) {
12651                 return text;
12652             }
12653
12654             if(sec >= 0) time = 600*time + 10*sec; else
12655             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12656
12657             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12658
12659             /* [HGM] PV time: now locate end of PV info */
12660             while( *++sep >= '0' && *sep <= '9'); // strip depth
12661             if(time >= 0)
12662             while( *++sep >= '0' && *sep <= '9'); // strip time
12663             if(sec >= 0)
12664             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12665             if(deci >= 0)
12666             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12667             while(*sep == ' ') sep++;
12668         }
12669
12670         if( depth <= 0 ) {
12671             return text;
12672         }
12673
12674         if( time < 0 ) {
12675             time = -1;
12676         }
12677
12678         pvInfoList[index-1].depth = depth;
12679         pvInfoList[index-1].score = score;
12680         pvInfoList[index-1].time  = 10*time; // centi-sec
12681         if(*sep == '}') *sep = 0; else *--sep = '{';
12682     }
12683     return sep;
12684 }
12685
12686 void
12687 SendToProgram(message, cps)
12688      char *message;
12689      ChessProgramState *cps;
12690 {
12691     int count, outCount, error;
12692     char buf[MSG_SIZ];
12693
12694     if (cps->pr == NULL) return;
12695     Attention(cps);
12696     
12697     if (appData.debugMode) {
12698         TimeMark now;
12699         GetTimeMark(&now);
12700         fprintf(debugFP, "%ld >%-6s: %s", 
12701                 SubtractTimeMarks(&now, &programStartTime),
12702                 cps->which, message);
12703     }
12704     
12705     count = strlen(message);
12706     outCount = OutputToProcess(cps->pr, message, count, &error);
12707     if (outCount < count && !exiting 
12708                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12709         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12710         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12711             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12712                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12713                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12714             } else {
12715                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12716             }
12717             gameInfo.resultDetails = StrSave(buf);
12718         }
12719         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12720     }
12721 }
12722
12723 void
12724 ReceiveFromProgram(isr, closure, message, count, error)
12725      InputSourceRef isr;
12726      VOIDSTAR closure;
12727      char *message;
12728      int count;
12729      int error;
12730 {
12731     char *end_str;
12732     char buf[MSG_SIZ];
12733     ChessProgramState *cps = (ChessProgramState *)closure;
12734
12735     if (isr != cps->isr) return; /* Killed intentionally */
12736     if (count <= 0) {
12737         if (count == 0) {
12738             sprintf(buf,
12739                     _("Error: %s chess program (%s) exited unexpectedly"),
12740                     cps->which, cps->program);
12741         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12742                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12743                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12744                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12745                 } else {
12746                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12747                 }
12748                 gameInfo.resultDetails = StrSave(buf);
12749             }
12750             RemoveInputSource(cps->isr);
12751             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12752         } else {
12753             sprintf(buf,
12754                     _("Error reading from %s chess program (%s)"),
12755                     cps->which, cps->program);
12756             RemoveInputSource(cps->isr);
12757
12758             /* [AS] Program is misbehaving badly... kill it */
12759             if( count == -2 ) {
12760                 DestroyChildProcess( cps->pr, 9 );
12761                 cps->pr = NoProc;
12762             }
12763
12764             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12765         }
12766         return;
12767     }
12768     
12769     if ((end_str = strchr(message, '\r')) != NULL)
12770       *end_str = NULLCHAR;
12771     if ((end_str = strchr(message, '\n')) != NULL)
12772       *end_str = NULLCHAR;
12773     
12774     if (appData.debugMode) {
12775         TimeMark now; int print = 1;
12776         char *quote = ""; char c; int i;
12777
12778         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12779                 char start = message[0];
12780                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12781                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12782                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12783                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12784                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12785                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12786                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12787                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12788                         { quote = "# "; print = (appData.engineComments == 2); }
12789                 message[0] = start; // restore original message
12790         }
12791         if(print) {
12792                 GetTimeMark(&now);
12793                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12794                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12795                         quote,
12796                         message);
12797         }
12798     }
12799
12800     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12801     if (appData.icsEngineAnalyze) {
12802         if (strstr(message, "whisper") != NULL ||
12803              strstr(message, "kibitz") != NULL || 
12804             strstr(message, "tellics") != NULL) return;
12805     }
12806
12807     HandleMachineMove(message, cps);
12808 }
12809
12810
12811 void
12812 SendTimeControl(cps, mps, tc, inc, sd, st)
12813      ChessProgramState *cps;
12814      int mps, inc, sd, st;
12815      long tc;
12816 {
12817     char buf[MSG_SIZ];
12818     int seconds;
12819
12820     if( timeControl_2 > 0 ) {
12821         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12822             tc = timeControl_2;
12823         }
12824     }
12825     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12826     inc /= cps->timeOdds;
12827     st  /= cps->timeOdds;
12828
12829     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12830
12831     if (st > 0) {
12832       /* Set exact time per move, normally using st command */
12833       if (cps->stKludge) {
12834         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12835         seconds = st % 60;
12836         if (seconds == 0) {
12837           sprintf(buf, "level 1 %d\n", st/60);
12838         } else {
12839           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12840         }
12841       } else {
12842         sprintf(buf, "st %d\n", st);
12843       }
12844     } else {
12845       /* Set conventional or incremental time control, using level command */
12846       if (seconds == 0) {
12847         /* Note old gnuchess bug -- minutes:seconds used to not work.
12848            Fixed in later versions, but still avoid :seconds
12849            when seconds is 0. */
12850         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12851       } else {
12852         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12853                 seconds, inc/1000);
12854       }
12855     }
12856     SendToProgram(buf, cps);
12857
12858     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12859     /* Orthogonally, limit search to given depth */
12860     if (sd > 0) {
12861       if (cps->sdKludge) {
12862         sprintf(buf, "depth\n%d\n", sd);
12863       } else {
12864         sprintf(buf, "sd %d\n", sd);
12865       }
12866       SendToProgram(buf, cps);
12867     }
12868
12869     if(cps->nps > 0) { /* [HGM] nps */
12870         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12871         else {
12872                 sprintf(buf, "nps %d\n", cps->nps);
12873               SendToProgram(buf, cps);
12874         }
12875     }
12876 }
12877
12878 ChessProgramState *WhitePlayer()
12879 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12880 {
12881     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12882        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12883         return &second;
12884     return &first;
12885 }
12886
12887 void
12888 SendTimeRemaining(cps, machineWhite)
12889      ChessProgramState *cps;
12890      int /*boolean*/ machineWhite;
12891 {
12892     char message[MSG_SIZ];
12893     long time, otime;
12894
12895     /* Note: this routine must be called when the clocks are stopped
12896        or when they have *just* been set or switched; otherwise
12897        it will be off by the time since the current tick started.
12898     */
12899     if (machineWhite) {
12900         time = whiteTimeRemaining / 10;
12901         otime = blackTimeRemaining / 10;
12902     } else {
12903         time = blackTimeRemaining / 10;
12904         otime = whiteTimeRemaining / 10;
12905     }
12906     /* [HGM] translate opponent's time by time-odds factor */
12907     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12908     if (appData.debugMode) {
12909         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12910     }
12911
12912     if (time <= 0) time = 1;
12913     if (otime <= 0) otime = 1;
12914     
12915     sprintf(message, "time %ld\n", time);
12916     SendToProgram(message, cps);
12917
12918     sprintf(message, "otim %ld\n", otime);
12919     SendToProgram(message, cps);
12920 }
12921
12922 int
12923 BoolFeature(p, name, loc, cps)
12924      char **p;
12925      char *name;
12926      int *loc;
12927      ChessProgramState *cps;
12928 {
12929   char buf[MSG_SIZ];
12930   int len = strlen(name);
12931   int val;
12932   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12933     (*p) += len + 1;
12934     sscanf(*p, "%d", &val);
12935     *loc = (val != 0);
12936     while (**p && **p != ' ') (*p)++;
12937     sprintf(buf, "accepted %s\n", name);
12938     SendToProgram(buf, cps);
12939     return TRUE;
12940   }
12941   return FALSE;
12942 }
12943
12944 int
12945 IntFeature(p, name, loc, cps)
12946      char **p;
12947      char *name;
12948      int *loc;
12949      ChessProgramState *cps;
12950 {
12951   char buf[MSG_SIZ];
12952   int len = strlen(name);
12953   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12954     (*p) += len + 1;
12955     sscanf(*p, "%d", loc);
12956     while (**p && **p != ' ') (*p)++;
12957     sprintf(buf, "accepted %s\n", name);
12958     SendToProgram(buf, cps);
12959     return TRUE;
12960   }
12961   return FALSE;
12962 }
12963
12964 int
12965 StringFeature(p, name, loc, cps)
12966      char **p;
12967      char *name;
12968      char loc[];
12969      ChessProgramState *cps;
12970 {
12971   char buf[MSG_SIZ];
12972   int len = strlen(name);
12973   if (strncmp((*p), name, len) == 0
12974       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12975     (*p) += len + 2;
12976     sscanf(*p, "%[^\"]", loc);
12977     while (**p && **p != '\"') (*p)++;
12978     if (**p == '\"') (*p)++;
12979     sprintf(buf, "accepted %s\n", name);
12980     SendToProgram(buf, cps);
12981     return TRUE;
12982   }
12983   return FALSE;
12984 }
12985
12986 int 
12987 ParseOption(Option *opt, ChessProgramState *cps)
12988 // [HGM] options: process the string that defines an engine option, and determine
12989 // name, type, default value, and allowed value range
12990 {
12991         char *p, *q, buf[MSG_SIZ];
12992         int n, min = (-1)<<31, max = 1<<31, def;
12993
12994         if(p = strstr(opt->name, " -spin ")) {
12995             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12996             if(max < min) max = min; // enforce consistency
12997             if(def < min) def = min;
12998             if(def > max) def = max;
12999             opt->value = def;
13000             opt->min = min;
13001             opt->max = max;
13002             opt->type = Spin;
13003         } else if((p = strstr(opt->name, " -slider "))) {
13004             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13005             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13006             if(max < min) max = min; // enforce consistency
13007             if(def < min) def = min;
13008             if(def > max) def = max;
13009             opt->value = def;
13010             opt->min = min;
13011             opt->max = max;
13012             opt->type = Spin; // Slider;
13013         } else if((p = strstr(opt->name, " -string "))) {
13014             opt->textValue = p+9;
13015             opt->type = TextBox;
13016         } else if((p = strstr(opt->name, " -file "))) {
13017             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13018             opt->textValue = p+7;
13019             opt->type = TextBox; // FileName;
13020         } else if((p = strstr(opt->name, " -path "))) {
13021             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13022             opt->textValue = p+7;
13023             opt->type = TextBox; // PathName;
13024         } else if(p = strstr(opt->name, " -check ")) {
13025             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13026             opt->value = (def != 0);
13027             opt->type = CheckBox;
13028         } else if(p = strstr(opt->name, " -combo ")) {
13029             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13030             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13031             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13032             opt->value = n = 0;
13033             while(q = StrStr(q, " /// ")) {
13034                 n++; *q = 0;    // count choices, and null-terminate each of them
13035                 q += 5;
13036                 if(*q == '*') { // remember default, which is marked with * prefix
13037                     q++;
13038                     opt->value = n;
13039                 }
13040                 cps->comboList[cps->comboCnt++] = q;
13041             }
13042             cps->comboList[cps->comboCnt++] = NULL;
13043             opt->max = n + 1;
13044             opt->type = ComboBox;
13045         } else if(p = strstr(opt->name, " -button")) {
13046             opt->type = Button;
13047         } else if(p = strstr(opt->name, " -save")) {
13048             opt->type = SaveButton;
13049         } else return FALSE;
13050         *p = 0; // terminate option name
13051         // now look if the command-line options define a setting for this engine option.
13052         if(cps->optionSettings && cps->optionSettings[0])
13053             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13054         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13055                 sprintf(buf, "option %s", p);
13056                 if(p = strstr(buf, ",")) *p = 0;
13057                 strcat(buf, "\n");
13058                 SendToProgram(buf, cps);
13059         }
13060         return TRUE;
13061 }
13062
13063 void
13064 FeatureDone(cps, val)
13065      ChessProgramState* cps;
13066      int val;
13067 {
13068   DelayedEventCallback cb = GetDelayedEvent();
13069   if ((cb == InitBackEnd3 && cps == &first) ||
13070       (cb == TwoMachinesEventIfReady && cps == &second)) {
13071     CancelDelayedEvent();
13072     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13073   }
13074   cps->initDone = val;
13075 }
13076
13077 /* Parse feature command from engine */
13078 void
13079 ParseFeatures(args, cps)
13080      char* args;
13081      ChessProgramState *cps;  
13082 {
13083   char *p = args;
13084   char *q;
13085   int val;
13086   char buf[MSG_SIZ];
13087
13088   for (;;) {
13089     while (*p == ' ') p++;
13090     if (*p == NULLCHAR) return;
13091
13092     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13093     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13094     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13095     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13096     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13097     if (BoolFeature(&p, "reuse", &val, cps)) {
13098       /* Engine can disable reuse, but can't enable it if user said no */
13099       if (!val) cps->reuse = FALSE;
13100       continue;
13101     }
13102     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13103     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13104       if (gameMode == TwoMachinesPlay) {
13105         DisplayTwoMachinesTitle();
13106       } else {
13107         DisplayTitle("");
13108       }
13109       continue;
13110     }
13111     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13112     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13113     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13114     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13115     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13116     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13117     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13118     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13119     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13120     if (IntFeature(&p, "done", &val, cps)) {
13121       FeatureDone(cps, val);
13122       continue;
13123     }
13124     /* Added by Tord: */
13125     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13126     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13127     /* End of additions by Tord */
13128
13129     /* [HGM] added features: */
13130     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13131     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13132     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13133     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13134     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13135     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13136     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13137         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13138             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13139             SendToProgram(buf, cps);
13140             continue;
13141         }
13142         if(cps->nrOptions >= MAX_OPTIONS) {
13143             cps->nrOptions--;
13144             sprintf(buf, "%s engine has too many options\n", cps->which);
13145             DisplayError(buf, 0);
13146         }
13147         continue;
13148     }
13149     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13150     /* End of additions by HGM */
13151
13152     /* unknown feature: complain and skip */
13153     q = p;
13154     while (*q && *q != '=') q++;
13155     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13156     SendToProgram(buf, cps);
13157     p = q;
13158     if (*p == '=') {
13159       p++;
13160       if (*p == '\"') {
13161         p++;
13162         while (*p && *p != '\"') p++;
13163         if (*p == '\"') p++;
13164       } else {
13165         while (*p && *p != ' ') p++;
13166       }
13167     }
13168   }
13169
13170 }
13171
13172 void
13173 PeriodicUpdatesEvent(newState)
13174      int newState;
13175 {
13176     if (newState == appData.periodicUpdates)
13177       return;
13178
13179     appData.periodicUpdates=newState;
13180
13181     /* Display type changes, so update it now */
13182 //    DisplayAnalysis();
13183
13184     /* Get the ball rolling again... */
13185     if (newState) {
13186         AnalysisPeriodicEvent(1);
13187         StartAnalysisClock();
13188     }
13189 }
13190
13191 void
13192 PonderNextMoveEvent(newState)
13193      int newState;
13194 {
13195     if (newState == appData.ponderNextMove) return;
13196     if (gameMode == EditPosition) EditPositionDone(TRUE);
13197     if (newState) {
13198         SendToProgram("hard\n", &first);
13199         if (gameMode == TwoMachinesPlay) {
13200             SendToProgram("hard\n", &second);
13201         }
13202     } else {
13203         SendToProgram("easy\n", &first);
13204         thinkOutput[0] = NULLCHAR;
13205         if (gameMode == TwoMachinesPlay) {
13206             SendToProgram("easy\n", &second);
13207         }
13208     }
13209     appData.ponderNextMove = newState;
13210 }
13211
13212 void
13213 NewSettingEvent(option, command, value)
13214      char *command;
13215      int option, value;
13216 {
13217     char buf[MSG_SIZ];
13218
13219     if (gameMode == EditPosition) EditPositionDone(TRUE);
13220     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13221     SendToProgram(buf, &first);
13222     if (gameMode == TwoMachinesPlay) {
13223         SendToProgram(buf, &second);
13224     }
13225 }
13226
13227 void
13228 ShowThinkingEvent()
13229 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13230 {
13231     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13232     int newState = appData.showThinking
13233         // [HGM] thinking: other features now need thinking output as well
13234         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13235     
13236     if (oldState == newState) return;
13237     oldState = newState;
13238     if (gameMode == EditPosition) EditPositionDone(TRUE);
13239     if (oldState) {
13240         SendToProgram("post\n", &first);
13241         if (gameMode == TwoMachinesPlay) {
13242             SendToProgram("post\n", &second);
13243         }
13244     } else {
13245         SendToProgram("nopost\n", &first);
13246         thinkOutput[0] = NULLCHAR;
13247         if (gameMode == TwoMachinesPlay) {
13248             SendToProgram("nopost\n", &second);
13249         }
13250     }
13251 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13252 }
13253
13254 void
13255 AskQuestionEvent(title, question, replyPrefix, which)
13256      char *title; char *question; char *replyPrefix; char *which;
13257 {
13258   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13259   if (pr == NoProc) return;
13260   AskQuestion(title, question, replyPrefix, pr);
13261 }
13262
13263 void
13264 DisplayMove(moveNumber)
13265      int moveNumber;
13266 {
13267     char message[MSG_SIZ];
13268     char res[MSG_SIZ];
13269     char cpThinkOutput[MSG_SIZ];
13270
13271     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13272     
13273     if (moveNumber == forwardMostMove - 1 || 
13274         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13275
13276         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13277
13278         if (strchr(cpThinkOutput, '\n')) {
13279             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13280         }
13281     } else {
13282         *cpThinkOutput = NULLCHAR;
13283     }
13284
13285     /* [AS] Hide thinking from human user */
13286     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13287         *cpThinkOutput = NULLCHAR;
13288         if( thinkOutput[0] != NULLCHAR ) {
13289             int i;
13290
13291             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13292                 cpThinkOutput[i] = '.';
13293             }
13294             cpThinkOutput[i] = NULLCHAR;
13295             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13296         }
13297     }
13298
13299     if (moveNumber == forwardMostMove - 1 &&
13300         gameInfo.resultDetails != NULL) {
13301         if (gameInfo.resultDetails[0] == NULLCHAR) {
13302             sprintf(res, " %s", PGNResult(gameInfo.result));
13303         } else {
13304             sprintf(res, " {%s} %s",
13305                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13306         }
13307     } else {
13308         res[0] = NULLCHAR;
13309     }
13310
13311     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13312         DisplayMessage(res, cpThinkOutput);
13313     } else {
13314         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13315                 WhiteOnMove(moveNumber) ? " " : ".. ",
13316                 parseList[moveNumber], res);
13317         DisplayMessage(message, cpThinkOutput);
13318     }
13319 }
13320
13321 void
13322 DisplayComment(moveNumber, text)
13323      int moveNumber;
13324      char *text;
13325 {
13326     char title[MSG_SIZ];
13327     char buf[8000]; // comment can be long!
13328     int score, depth;
13329     
13330     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13331       strcpy(title, "Comment");
13332     } else {
13333       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13334               WhiteOnMove(moveNumber) ? " " : ".. ",
13335               parseList[moveNumber]);
13336     }
13337     // [HGM] PV info: display PV info together with (or as) comment
13338     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13339       if(text == NULL) text = "";                                           
13340       score = pvInfoList[moveNumber].score;
13341       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13342               depth, (pvInfoList[moveNumber].time+50)/100, text);
13343       text = buf;
13344     }
13345     if (text != NULL && (appData.autoDisplayComment || commentUp))
13346         CommentPopUp(title, text);
13347 }
13348
13349 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13350  * might be busy thinking or pondering.  It can be omitted if your
13351  * gnuchess is configured to stop thinking immediately on any user
13352  * input.  However, that gnuchess feature depends on the FIONREAD
13353  * ioctl, which does not work properly on some flavors of Unix.
13354  */
13355 void
13356 Attention(cps)
13357      ChessProgramState *cps;
13358 {
13359 #if ATTENTION
13360     if (!cps->useSigint) return;
13361     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13362     switch (gameMode) {
13363       case MachinePlaysWhite:
13364       case MachinePlaysBlack:
13365       case TwoMachinesPlay:
13366       case IcsPlayingWhite:
13367       case IcsPlayingBlack:
13368       case AnalyzeMode:
13369       case AnalyzeFile:
13370         /* Skip if we know it isn't thinking */
13371         if (!cps->maybeThinking) return;
13372         if (appData.debugMode)
13373           fprintf(debugFP, "Interrupting %s\n", cps->which);
13374         InterruptChildProcess(cps->pr);
13375         cps->maybeThinking = FALSE;
13376         break;
13377       default:
13378         break;
13379     }
13380 #endif /*ATTENTION*/
13381 }
13382
13383 int
13384 CheckFlags()
13385 {
13386     if (whiteTimeRemaining <= 0) {
13387         if (!whiteFlag) {
13388             whiteFlag = TRUE;
13389             if (appData.icsActive) {
13390                 if (appData.autoCallFlag &&
13391                     gameMode == IcsPlayingBlack && !blackFlag) {
13392                   SendToICS(ics_prefix);
13393                   SendToICS("flag\n");
13394                 }
13395             } else {
13396                 if (blackFlag) {
13397                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13398                 } else {
13399                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13400                     if (appData.autoCallFlag) {
13401                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13402                         return TRUE;
13403                     }
13404                 }
13405             }
13406         }
13407     }
13408     if (blackTimeRemaining <= 0) {
13409         if (!blackFlag) {
13410             blackFlag = TRUE;
13411             if (appData.icsActive) {
13412                 if (appData.autoCallFlag &&
13413                     gameMode == IcsPlayingWhite && !whiteFlag) {
13414                   SendToICS(ics_prefix);
13415                   SendToICS("flag\n");
13416                 }
13417             } else {
13418                 if (whiteFlag) {
13419                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13420                 } else {
13421                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13422                     if (appData.autoCallFlag) {
13423                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13424                         return TRUE;
13425                     }
13426                 }
13427             }
13428         }
13429     }
13430     return FALSE;
13431 }
13432
13433 void
13434 CheckTimeControl()
13435 {
13436     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13437         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13438
13439     /*
13440      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13441      */
13442     if ( !WhiteOnMove(forwardMostMove) )
13443         /* White made time control */
13444         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13445         /* [HGM] time odds: correct new time quota for time odds! */
13446                                             / WhitePlayer()->timeOdds;
13447       else
13448         /* Black made time control */
13449         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13450                                             / WhitePlayer()->other->timeOdds;
13451 }
13452
13453 void
13454 DisplayBothClocks()
13455 {
13456     int wom = gameMode == EditPosition ?
13457       !blackPlaysFirst : WhiteOnMove(currentMove);
13458     DisplayWhiteClock(whiteTimeRemaining, wom);
13459     DisplayBlackClock(blackTimeRemaining, !wom);
13460 }
13461
13462
13463 /* Timekeeping seems to be a portability nightmare.  I think everyone
13464    has ftime(), but I'm really not sure, so I'm including some ifdefs
13465    to use other calls if you don't.  Clocks will be less accurate if
13466    you have neither ftime nor gettimeofday.
13467 */
13468
13469 /* VS 2008 requires the #include outside of the function */
13470 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13471 #include <sys/timeb.h>
13472 #endif
13473
13474 /* Get the current time as a TimeMark */
13475 void
13476 GetTimeMark(tm)
13477      TimeMark *tm;
13478 {
13479 #if HAVE_GETTIMEOFDAY
13480
13481     struct timeval timeVal;
13482     struct timezone timeZone;
13483
13484     gettimeofday(&timeVal, &timeZone);
13485     tm->sec = (long) timeVal.tv_sec; 
13486     tm->ms = (int) (timeVal.tv_usec / 1000L);
13487
13488 #else /*!HAVE_GETTIMEOFDAY*/
13489 #if HAVE_FTIME
13490
13491 // include <sys/timeb.h> / moved to just above start of function
13492     struct timeb timeB;
13493
13494     ftime(&timeB);
13495     tm->sec = (long) timeB.time;
13496     tm->ms = (int) timeB.millitm;
13497
13498 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13499     tm->sec = (long) time(NULL);
13500     tm->ms = 0;
13501 #endif
13502 #endif
13503 }
13504
13505 /* Return the difference in milliseconds between two
13506    time marks.  We assume the difference will fit in a long!
13507 */
13508 long
13509 SubtractTimeMarks(tm2, tm1)
13510      TimeMark *tm2, *tm1;
13511 {
13512     return 1000L*(tm2->sec - tm1->sec) +
13513            (long) (tm2->ms - tm1->ms);
13514 }
13515
13516
13517 /*
13518  * Code to manage the game clocks.
13519  *
13520  * In tournament play, black starts the clock and then white makes a move.
13521  * We give the human user a slight advantage if he is playing white---the
13522  * clocks don't run until he makes his first move, so it takes zero time.
13523  * Also, we don't account for network lag, so we could get out of sync
13524  * with GNU Chess's clock -- but then, referees are always right.  
13525  */
13526
13527 static TimeMark tickStartTM;
13528 static long intendedTickLength;
13529
13530 long
13531 NextTickLength(timeRemaining)
13532      long timeRemaining;
13533 {
13534     long nominalTickLength, nextTickLength;
13535
13536     if (timeRemaining > 0L && timeRemaining <= 10000L)
13537       nominalTickLength = 100L;
13538     else
13539       nominalTickLength = 1000L;
13540     nextTickLength = timeRemaining % nominalTickLength;
13541     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13542
13543     return nextTickLength;
13544 }
13545
13546 /* Adjust clock one minute up or down */
13547 void
13548 AdjustClock(Boolean which, int dir)
13549 {
13550     if(which) blackTimeRemaining += 60000*dir;
13551     else      whiteTimeRemaining += 60000*dir;
13552     DisplayBothClocks();
13553 }
13554
13555 /* Stop clocks and reset to a fresh time control */
13556 void
13557 ResetClocks() 
13558 {
13559     (void) StopClockTimer();
13560     if (appData.icsActive) {
13561         whiteTimeRemaining = blackTimeRemaining = 0;
13562     } else if (searchTime) {
13563         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13564         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13565     } else { /* [HGM] correct new time quote for time odds */
13566         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13567         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13568     }
13569     if (whiteFlag || blackFlag) {
13570         DisplayTitle("");
13571         whiteFlag = blackFlag = FALSE;
13572     }
13573     DisplayBothClocks();
13574 }
13575
13576 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13577
13578 /* Decrement running clock by amount of time that has passed */
13579 void
13580 DecrementClocks()
13581 {
13582     long timeRemaining;
13583     long lastTickLength, fudge;
13584     TimeMark now;
13585
13586     if (!appData.clockMode) return;
13587     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13588         
13589     GetTimeMark(&now);
13590
13591     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13592
13593     /* Fudge if we woke up a little too soon */
13594     fudge = intendedTickLength - lastTickLength;
13595     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13596
13597     if (WhiteOnMove(forwardMostMove)) {
13598         if(whiteNPS >= 0) lastTickLength = 0;
13599         timeRemaining = whiteTimeRemaining -= lastTickLength;
13600         DisplayWhiteClock(whiteTimeRemaining - fudge,
13601                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13602     } else {
13603         if(blackNPS >= 0) lastTickLength = 0;
13604         timeRemaining = blackTimeRemaining -= lastTickLength;
13605         DisplayBlackClock(blackTimeRemaining - fudge,
13606                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13607     }
13608
13609     if (CheckFlags()) return;
13610         
13611     tickStartTM = now;
13612     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13613     StartClockTimer(intendedTickLength);
13614
13615     /* if the time remaining has fallen below the alarm threshold, sound the
13616      * alarm. if the alarm has sounded and (due to a takeback or time control
13617      * with increment) the time remaining has increased to a level above the
13618      * threshold, reset the alarm so it can sound again. 
13619      */
13620     
13621     if (appData.icsActive && appData.icsAlarm) {
13622
13623         /* make sure we are dealing with the user's clock */
13624         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13625                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13626            )) return;
13627
13628         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13629             alarmSounded = FALSE;
13630         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13631             PlayAlarmSound();
13632             alarmSounded = TRUE;
13633         }
13634     }
13635 }
13636
13637
13638 /* A player has just moved, so stop the previously running
13639    clock and (if in clock mode) start the other one.
13640    We redisplay both clocks in case we're in ICS mode, because
13641    ICS gives us an update to both clocks after every move.
13642    Note that this routine is called *after* forwardMostMove
13643    is updated, so the last fractional tick must be subtracted
13644    from the color that is *not* on move now.
13645 */
13646 void
13647 SwitchClocks()
13648 {
13649     long lastTickLength;
13650     TimeMark now;
13651     int flagged = FALSE;
13652
13653     GetTimeMark(&now);
13654
13655     if (StopClockTimer() && appData.clockMode) {
13656         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13657         if (WhiteOnMove(forwardMostMove)) {
13658             if(blackNPS >= 0) lastTickLength = 0;
13659             blackTimeRemaining -= lastTickLength;
13660            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13661 //         if(pvInfoList[forwardMostMove-1].time == -1)
13662                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13663                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13664         } else {
13665            if(whiteNPS >= 0) lastTickLength = 0;
13666            whiteTimeRemaining -= lastTickLength;
13667            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13668 //         if(pvInfoList[forwardMostMove-1].time == -1)
13669                  pvInfoList[forwardMostMove-1].time = 
13670                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13671         }
13672         flagged = CheckFlags();
13673     }
13674     CheckTimeControl();
13675
13676     if (flagged || !appData.clockMode) return;
13677
13678     switch (gameMode) {
13679       case MachinePlaysBlack:
13680       case MachinePlaysWhite:
13681       case BeginningOfGame:
13682         if (pausing) return;
13683         break;
13684
13685       case EditGame:
13686       case PlayFromGameFile:
13687       case IcsExamining:
13688         return;
13689
13690       default:
13691         break;
13692     }
13693
13694     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13695         if(WhiteOnMove(forwardMostMove))
13696              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13697         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13698     }
13699
13700     tickStartTM = now;
13701     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13702       whiteTimeRemaining : blackTimeRemaining);
13703     StartClockTimer(intendedTickLength);
13704 }
13705         
13706
13707 /* Stop both clocks */
13708 void
13709 StopClocks()
13710 {       
13711     long lastTickLength;
13712     TimeMark now;
13713
13714     if (!StopClockTimer()) return;
13715     if (!appData.clockMode) return;
13716
13717     GetTimeMark(&now);
13718
13719     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13720     if (WhiteOnMove(forwardMostMove)) {
13721         if(whiteNPS >= 0) lastTickLength = 0;
13722         whiteTimeRemaining -= lastTickLength;
13723         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13724     } else {
13725         if(blackNPS >= 0) lastTickLength = 0;
13726         blackTimeRemaining -= lastTickLength;
13727         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13728     }
13729     CheckFlags();
13730 }
13731         
13732 /* Start clock of player on move.  Time may have been reset, so
13733    if clock is already running, stop and restart it. */
13734 void
13735 StartClocks()
13736 {
13737     (void) StopClockTimer(); /* in case it was running already */
13738     DisplayBothClocks();
13739     if (CheckFlags()) return;
13740
13741     if (!appData.clockMode) return;
13742     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13743
13744     GetTimeMark(&tickStartTM);
13745     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13746       whiteTimeRemaining : blackTimeRemaining);
13747
13748    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13749     whiteNPS = blackNPS = -1; 
13750     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13751        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13752         whiteNPS = first.nps;
13753     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13754        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13755         blackNPS = first.nps;
13756     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13757         whiteNPS = second.nps;
13758     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13759         blackNPS = second.nps;
13760     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13761
13762     StartClockTimer(intendedTickLength);
13763 }
13764
13765 char *
13766 TimeString(ms)
13767      long ms;
13768 {
13769     long second, minute, hour, day;
13770     char *sign = "";
13771     static char buf[32];
13772     
13773     if (ms > 0 && ms <= 9900) {
13774       /* convert milliseconds to tenths, rounding up */
13775       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13776
13777       sprintf(buf, " %03.1f ", tenths/10.0);
13778       return buf;
13779     }
13780
13781     /* convert milliseconds to seconds, rounding up */
13782     /* use floating point to avoid strangeness of integer division
13783        with negative dividends on many machines */
13784     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13785
13786     if (second < 0) {
13787         sign = "-";
13788         second = -second;
13789     }
13790     
13791     day = second / (60 * 60 * 24);
13792     second = second % (60 * 60 * 24);
13793     hour = second / (60 * 60);
13794     second = second % (60 * 60);
13795     minute = second / 60;
13796     second = second % 60;
13797     
13798     if (day > 0)
13799       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13800               sign, day, hour, minute, second);
13801     else if (hour > 0)
13802       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13803     else
13804       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13805     
13806     return buf;
13807 }
13808
13809
13810 /*
13811  * This is necessary because some C libraries aren't ANSI C compliant yet.
13812  */
13813 char *
13814 StrStr(string, match)
13815      char *string, *match;
13816 {
13817     int i, length;
13818     
13819     length = strlen(match);
13820     
13821     for (i = strlen(string) - length; i >= 0; i--, string++)
13822       if (!strncmp(match, string, length))
13823         return string;
13824     
13825     return NULL;
13826 }
13827
13828 char *
13829 StrCaseStr(string, match)
13830      char *string, *match;
13831 {
13832     int i, j, length;
13833     
13834     length = strlen(match);
13835     
13836     for (i = strlen(string) - length; i >= 0; i--, string++) {
13837         for (j = 0; j < length; j++) {
13838             if (ToLower(match[j]) != ToLower(string[j]))
13839               break;
13840         }
13841         if (j == length) return string;
13842     }
13843
13844     return NULL;
13845 }
13846
13847 #ifndef _amigados
13848 int
13849 StrCaseCmp(s1, s2)
13850      char *s1, *s2;
13851 {
13852     char c1, c2;
13853     
13854     for (;;) {
13855         c1 = ToLower(*s1++);
13856         c2 = ToLower(*s2++);
13857         if (c1 > c2) return 1;
13858         if (c1 < c2) return -1;
13859         if (c1 == NULLCHAR) return 0;
13860     }
13861 }
13862
13863
13864 int
13865 ToLower(c)
13866      int c;
13867 {
13868     return isupper(c) ? tolower(c) : c;
13869 }
13870
13871
13872 int
13873 ToUpper(c)
13874      int c;
13875 {
13876     return islower(c) ? toupper(c) : c;
13877 }
13878 #endif /* !_amigados    */
13879
13880 char *
13881 StrSave(s)
13882      char *s;
13883 {
13884     char *ret;
13885
13886     if ((ret = (char *) malloc(strlen(s) + 1))) {
13887         strcpy(ret, s);
13888     }
13889     return ret;
13890 }
13891
13892 char *
13893 StrSavePtr(s, savePtr)
13894      char *s, **savePtr;
13895 {
13896     if (*savePtr) {
13897         free(*savePtr);
13898     }
13899     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13900         strcpy(*savePtr, s);
13901     }
13902     return(*savePtr);
13903 }
13904
13905 char *
13906 PGNDate()
13907 {
13908     time_t clock;
13909     struct tm *tm;
13910     char buf[MSG_SIZ];
13911
13912     clock = time((time_t *)NULL);
13913     tm = localtime(&clock);
13914     sprintf(buf, "%04d.%02d.%02d",
13915             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13916     return StrSave(buf);
13917 }
13918
13919
13920 char *
13921 PositionToFEN(move, overrideCastling)
13922      int move;
13923      char *overrideCastling;
13924 {
13925     int i, j, fromX, fromY, toX, toY;
13926     int whiteToPlay;
13927     char buf[128];
13928     char *p, *q;
13929     int emptycount;
13930     ChessSquare piece;
13931
13932     whiteToPlay = (gameMode == EditPosition) ?
13933       !blackPlaysFirst : (move % 2 == 0);
13934     p = buf;
13935
13936     /* Piece placement data */
13937     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13938         emptycount = 0;
13939         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13940             if (boards[move][i][j] == EmptySquare) {
13941                 emptycount++;
13942             } else { ChessSquare piece = boards[move][i][j];
13943                 if (emptycount > 0) {
13944                     if(emptycount<10) /* [HGM] can be >= 10 */
13945                         *p++ = '0' + emptycount;
13946                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13947                     emptycount = 0;
13948                 }
13949                 if(PieceToChar(piece) == '+') {
13950                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13951                     *p++ = '+';
13952                     piece = (ChessSquare)(DEMOTED piece);
13953                 } 
13954                 *p++ = PieceToChar(piece);
13955                 if(p[-1] == '~') {
13956                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13957                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13958                     *p++ = '~';
13959                 }
13960             }
13961         }
13962         if (emptycount > 0) {
13963             if(emptycount<10) /* [HGM] can be >= 10 */
13964                 *p++ = '0' + emptycount;
13965             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13966             emptycount = 0;
13967         }
13968         *p++ = '/';
13969     }
13970     *(p - 1) = ' ';
13971
13972     /* [HGM] print Crazyhouse or Shogi holdings */
13973     if( gameInfo.holdingsWidth ) {
13974         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13975         q = p;
13976         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13977             piece = boards[move][i][BOARD_WIDTH-1];
13978             if( piece != EmptySquare )
13979               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13980                   *p++ = PieceToChar(piece);
13981         }
13982         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13983             piece = boards[move][BOARD_HEIGHT-i-1][0];
13984             if( piece != EmptySquare )
13985               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13986                   *p++ = PieceToChar(piece);
13987         }
13988
13989         if( q == p ) *p++ = '-';
13990         *p++ = ']';
13991         *p++ = ' ';
13992     }
13993
13994     /* Active color */
13995     *p++ = whiteToPlay ? 'w' : 'b';
13996     *p++ = ' ';
13997
13998   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13999     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14000   } else {
14001   if(nrCastlingRights) {
14002      q = p;
14003      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14004        /* [HGM] write directly from rights */
14005            if(boards[move][CASTLING][2] != NoRights &&
14006               boards[move][CASTLING][0] != NoRights   )
14007                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14008            if(boards[move][CASTLING][2] != NoRights &&
14009               boards[move][CASTLING][1] != NoRights   )
14010                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14011            if(boards[move][CASTLING][5] != NoRights &&
14012               boards[move][CASTLING][3] != NoRights   )
14013                 *p++ = boards[move][CASTLING][3] + AAA;
14014            if(boards[move][CASTLING][5] != NoRights &&
14015               boards[move][CASTLING][4] != NoRights   )
14016                 *p++ = boards[move][CASTLING][4] + AAA;
14017      } else {
14018
14019         /* [HGM] write true castling rights */
14020         if( nrCastlingRights == 6 ) {
14021             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14022                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14023             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14024                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14025             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14026                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14027             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14028                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14029         }
14030      }
14031      if (q == p) *p++ = '-'; /* No castling rights */
14032      *p++ = ' ';
14033   }
14034
14035   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14036      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14037     /* En passant target square */
14038     if (move > backwardMostMove) {
14039         fromX = moveList[move - 1][0] - AAA;
14040         fromY = moveList[move - 1][1] - ONE;
14041         toX = moveList[move - 1][2] - AAA;
14042         toY = moveList[move - 1][3] - ONE;
14043         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14044             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14045             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14046             fromX == toX) {
14047             /* 2-square pawn move just happened */
14048             *p++ = toX + AAA;
14049             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14050         } else {
14051             *p++ = '-';
14052         }
14053     } else if(move == backwardMostMove) {
14054         // [HGM] perhaps we should always do it like this, and forget the above?
14055         if((signed char)boards[move][EP_STATUS] >= 0) {
14056             *p++ = boards[move][EP_STATUS] + AAA;
14057             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14058         } else {
14059             *p++ = '-';
14060         }
14061     } else {
14062         *p++ = '-';
14063     }
14064     *p++ = ' ';
14065   }
14066   }
14067
14068     /* [HGM] find reversible plies */
14069     {   int i = 0, j=move;
14070
14071         if (appData.debugMode) { int k;
14072             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14073             for(k=backwardMostMove; k<=forwardMostMove; k++)
14074                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14075
14076         }
14077
14078         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14079         if( j == backwardMostMove ) i += initialRulePlies;
14080         sprintf(p, "%d ", i);
14081         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14082     }
14083     /* Fullmove number */
14084     sprintf(p, "%d", (move / 2) + 1);
14085     
14086     return StrSave(buf);
14087 }
14088
14089 Boolean
14090 ParseFEN(board, blackPlaysFirst, fen)
14091     Board board;
14092      int *blackPlaysFirst;
14093      char *fen;
14094 {
14095     int i, j;
14096     char *p;
14097     int emptycount;
14098     ChessSquare piece;
14099
14100     p = fen;
14101
14102     /* [HGM] by default clear Crazyhouse holdings, if present */
14103     if(gameInfo.holdingsWidth) {
14104        for(i=0; i<BOARD_HEIGHT; i++) {
14105            board[i][0]             = EmptySquare; /* black holdings */
14106            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14107            board[i][1]             = (ChessSquare) 0; /* black counts */
14108            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14109        }
14110     }
14111
14112     /* Piece placement data */
14113     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14114         j = 0;
14115         for (;;) {
14116             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14117                 if (*p == '/') p++;
14118                 emptycount = gameInfo.boardWidth - j;
14119                 while (emptycount--)
14120                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14121                 break;
14122 #if(BOARD_FILES >= 10)
14123             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14124                 p++; emptycount=10;
14125                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14126                 while (emptycount--)
14127                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14128 #endif
14129             } else if (isdigit(*p)) {
14130                 emptycount = *p++ - '0';
14131                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14132                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14133                 while (emptycount--)
14134                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14135             } else if (*p == '+' || isalpha(*p)) {
14136                 if (j >= gameInfo.boardWidth) return FALSE;
14137                 if(*p=='+') {
14138                     piece = CharToPiece(*++p);
14139                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14140                     piece = (ChessSquare) (PROMOTED piece ); p++;
14141                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14142                 } else piece = CharToPiece(*p++);
14143
14144                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14145                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14146                     piece = (ChessSquare) (PROMOTED piece);
14147                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14148                     p++;
14149                 }
14150                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14151             } else {
14152                 return FALSE;
14153             }
14154         }
14155     }
14156     while (*p == '/' || *p == ' ') p++;
14157
14158     /* [HGM] look for Crazyhouse holdings here */
14159     while(*p==' ') p++;
14160     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14161         if(*p == '[') p++;
14162         if(*p == '-' ) *p++; /* empty holdings */ else {
14163             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14164             /* if we would allow FEN reading to set board size, we would   */
14165             /* have to add holdings and shift the board read so far here   */
14166             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14167                 *p++;
14168                 if((int) piece >= (int) BlackPawn ) {
14169                     i = (int)piece - (int)BlackPawn;
14170                     i = PieceToNumber((ChessSquare)i);
14171                     if( i >= gameInfo.holdingsSize ) return FALSE;
14172                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14173                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14174                 } else {
14175                     i = (int)piece - (int)WhitePawn;
14176                     i = PieceToNumber((ChessSquare)i);
14177                     if( i >= gameInfo.holdingsSize ) return FALSE;
14178                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14179                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14180                 }
14181             }
14182         }
14183         if(*p == ']') *p++;
14184     }
14185
14186     while(*p == ' ') p++;
14187
14188     /* Active color */
14189     switch (*p++) {
14190       case 'w':
14191         *blackPlaysFirst = FALSE;
14192         break;
14193       case 'b': 
14194         *blackPlaysFirst = TRUE;
14195         break;
14196       default:
14197         return FALSE;
14198     }
14199
14200     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14201     /* return the extra info in global variiables             */
14202
14203     /* set defaults in case FEN is incomplete */
14204     board[EP_STATUS] = EP_UNKNOWN;
14205     for(i=0; i<nrCastlingRights; i++ ) {
14206         board[CASTLING][i] =
14207             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14208     }   /* assume possible unless obviously impossible */
14209     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14210     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14211     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14212                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14213     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14214     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14215     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14216                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14217     FENrulePlies = 0;
14218
14219     while(*p==' ') p++;
14220     if(nrCastlingRights) {
14221       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14222           /* castling indicator present, so default becomes no castlings */
14223           for(i=0; i<nrCastlingRights; i++ ) {
14224                  board[CASTLING][i] = NoRights;
14225           }
14226       }
14227       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14228              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14229              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14230              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14231         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14232
14233         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14234             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14235             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14236         }
14237         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14238             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14239         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14240                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14241         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14242                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14243         switch(c) {
14244           case'K':
14245               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14246               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14247               board[CASTLING][2] = whiteKingFile;
14248               break;
14249           case'Q':
14250               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14251               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14252               board[CASTLING][2] = whiteKingFile;
14253               break;
14254           case'k':
14255               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14256               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14257               board[CASTLING][5] = blackKingFile;
14258               break;
14259           case'q':
14260               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14261               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14262               board[CASTLING][5] = blackKingFile;
14263           case '-':
14264               break;
14265           default: /* FRC castlings */
14266               if(c >= 'a') { /* black rights */
14267                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14268                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14269                   if(i == BOARD_RGHT) break;
14270                   board[CASTLING][5] = i;
14271                   c -= AAA;
14272                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14273                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14274                   if(c > i)
14275                       board[CASTLING][3] = c;
14276                   else
14277                       board[CASTLING][4] = c;
14278               } else { /* white rights */
14279                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14280                     if(board[0][i] == WhiteKing) break;
14281                   if(i == BOARD_RGHT) break;
14282                   board[CASTLING][2] = i;
14283                   c -= AAA - 'a' + 'A';
14284                   if(board[0][c] >= WhiteKing) break;
14285                   if(c > i)
14286                       board[CASTLING][0] = c;
14287                   else
14288                       board[CASTLING][1] = c;
14289               }
14290         }
14291       }
14292       for(i=0; i<nrCastlingRights; i++)
14293         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14294     if (appData.debugMode) {
14295         fprintf(debugFP, "FEN castling rights:");
14296         for(i=0; i<nrCastlingRights; i++)
14297         fprintf(debugFP, " %d", board[CASTLING][i]);
14298         fprintf(debugFP, "\n");
14299     }
14300
14301       while(*p==' ') p++;
14302     }
14303
14304     /* read e.p. field in games that know e.p. capture */
14305     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14306        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14307       if(*p=='-') {
14308         p++; board[EP_STATUS] = EP_NONE;
14309       } else {
14310          char c = *p++ - AAA;
14311
14312          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14313          if(*p >= '0' && *p <='9') *p++;
14314          board[EP_STATUS] = c;
14315       }
14316     }
14317
14318
14319     if(sscanf(p, "%d", &i) == 1) {
14320         FENrulePlies = i; /* 50-move ply counter */
14321         /* (The move number is still ignored)    */
14322     }
14323
14324     return TRUE;
14325 }
14326       
14327 void
14328 EditPositionPasteFEN(char *fen)
14329 {
14330   if (fen != NULL) {
14331     Board initial_position;
14332
14333     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14334       DisplayError(_("Bad FEN position in clipboard"), 0);
14335       return ;
14336     } else {
14337       int savedBlackPlaysFirst = blackPlaysFirst;
14338       EditPositionEvent();
14339       blackPlaysFirst = savedBlackPlaysFirst;
14340       CopyBoard(boards[0], initial_position);
14341       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14342       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14343       DisplayBothClocks();
14344       DrawPosition(FALSE, boards[currentMove]);
14345     }
14346   }
14347 }
14348
14349 static char cseq[12] = "\\   ";
14350
14351 Boolean set_cont_sequence(char *new_seq)
14352 {
14353     int len;
14354     Boolean ret;
14355
14356     // handle bad attempts to set the sequence
14357         if (!new_seq)
14358                 return 0; // acceptable error - no debug
14359
14360     len = strlen(new_seq);
14361     ret = (len > 0) && (len < sizeof(cseq));
14362     if (ret)
14363         strcpy(cseq, new_seq);
14364     else if (appData.debugMode)
14365         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14366     return ret;
14367 }
14368
14369 /*
14370     reformat a source message so words don't cross the width boundary.  internal
14371     newlines are not removed.  returns the wrapped size (no null character unless
14372     included in source message).  If dest is NULL, only calculate the size required
14373     for the dest buffer.  lp argument indicats line position upon entry, and it's
14374     passed back upon exit.
14375 */
14376 int wrap(char *dest, char *src, int count, int width, int *lp)
14377 {
14378     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14379
14380     cseq_len = strlen(cseq);
14381     old_line = line = *lp;
14382     ansi = len = clen = 0;
14383
14384     for (i=0; i < count; i++)
14385     {
14386         if (src[i] == '\033')
14387             ansi = 1;
14388
14389         // if we hit the width, back up
14390         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14391         {
14392             // store i & len in case the word is too long
14393             old_i = i, old_len = len;
14394
14395             // find the end of the last word
14396             while (i && src[i] != ' ' && src[i] != '\n')
14397             {
14398                 i--;
14399                 len--;
14400             }
14401
14402             // word too long?  restore i & len before splitting it
14403             if ((old_i-i+clen) >= width)
14404             {
14405                 i = old_i;
14406                 len = old_len;
14407             }
14408
14409             // extra space?
14410             if (i && src[i-1] == ' ')
14411                 len--;
14412
14413             if (src[i] != ' ' && src[i] != '\n')
14414             {
14415                 i--;
14416                 if (len)
14417                     len--;
14418             }
14419
14420             // now append the newline and continuation sequence
14421             if (dest)
14422                 dest[len] = '\n';
14423             len++;
14424             if (dest)
14425                 strncpy(dest+len, cseq, cseq_len);
14426             len += cseq_len;
14427             line = cseq_len;
14428             clen = cseq_len;
14429             continue;
14430         }
14431
14432         if (dest)
14433             dest[len] = src[i];
14434         len++;
14435         if (!ansi)
14436             line++;
14437         if (src[i] == '\n')
14438             line = 0;
14439         if (src[i] == 'm')
14440             ansi = 0;
14441     }
14442     if (dest && appData.debugMode)
14443     {
14444         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14445             count, width, line, len, *lp);
14446         show_bytes(debugFP, src, count);
14447         fprintf(debugFP, "\ndest: ");
14448         show_bytes(debugFP, dest, len);
14449         fprintf(debugFP, "\n");
14450     }
14451     *lp = dest ? line : old_line;
14452
14453     return len;
14454 }
14455
14456 // [HGM] vari: routines for shelving variations
14457
14458 void 
14459 PushTail(int firstMove, int lastMove)
14460 {
14461         int i, j, nrMoves = lastMove - firstMove;
14462
14463         if(appData.icsActive) { // only in local mode
14464                 forwardMostMove = currentMove; // mimic old ICS behavior
14465                 return;
14466         }
14467         if(storedGames >= MAX_VARIATIONS-1) return;
14468
14469         // push current tail of game on stack
14470         savedResult[storedGames] = gameInfo.result;
14471         savedDetails[storedGames] = gameInfo.resultDetails;
14472         gameInfo.resultDetails = NULL;
14473         savedFirst[storedGames] = firstMove;
14474         savedLast [storedGames] = lastMove;
14475         savedFramePtr[storedGames] = framePtr;
14476         framePtr -= nrMoves; // reserve space for the boards
14477         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14478             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14479             for(j=0; j<MOVE_LEN; j++)
14480                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14481             for(j=0; j<2*MOVE_LEN; j++)
14482                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14483             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14484             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14485             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14486             pvInfoList[firstMove+i-1].depth = 0;
14487             commentList[framePtr+i] = commentList[firstMove+i];
14488             commentList[firstMove+i] = NULL;
14489         }
14490
14491         storedGames++;
14492         forwardMostMove = currentMove; // truncte game so we can start variation
14493         if(storedGames == 1) GreyRevert(FALSE);
14494 }
14495
14496 Boolean
14497 PopTail(Boolean annotate)
14498 {
14499         int i, j, nrMoves;
14500         char buf[8000], moveBuf[20];
14501
14502         if(appData.icsActive) return FALSE; // only in local mode
14503         if(!storedGames) return FALSE; // sanity
14504
14505         storedGames--;
14506         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14507         nrMoves = savedLast[storedGames] - currentMove;
14508         if(annotate) {
14509                 int cnt = 10;
14510                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14511                 else strcpy(buf, "(");
14512                 for(i=currentMove; i<forwardMostMove; i++) {
14513                         if(WhiteOnMove(i))
14514                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14515                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14516                         strcat(buf, moveBuf);
14517                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14518                 }
14519                 strcat(buf, ")");
14520         }
14521         for(i=1; i<nrMoves; i++) { // copy last variation back
14522             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14523             for(j=0; j<MOVE_LEN; j++)
14524                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14525             for(j=0; j<2*MOVE_LEN; j++)
14526                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14527             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14528             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14529             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14530             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14531             commentList[currentMove+i] = commentList[framePtr+i];
14532             commentList[framePtr+i] = NULL;
14533         }
14534         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14535         framePtr = savedFramePtr[storedGames];
14536         gameInfo.result = savedResult[storedGames];
14537         if(gameInfo.resultDetails != NULL) {
14538             free(gameInfo.resultDetails);
14539       }
14540         gameInfo.resultDetails = savedDetails[storedGames];
14541         forwardMostMove = currentMove + nrMoves;
14542         if(storedGames == 0) GreyRevert(TRUE);
14543         return TRUE;
14544 }
14545
14546 void 
14547 CleanupTail()
14548 {       // remove all shelved variations
14549         int i;
14550         for(i=0; i<storedGames; i++) {
14551             if(savedDetails[i])
14552                 free(savedDetails[i]);
14553             savedDetails[i] = NULL;
14554         }
14555         for(i=framePtr; i<MAX_MOVES; i++) {
14556                 if(commentList[i]) free(commentList[i]);
14557                 commentList[i] = NULL;
14558         }
14559         framePtr = MAX_MOVES-1;
14560         storedGames = 0;
14561 }