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