Allow popup-less (fatal) exit of engine after tellusererror
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char  initialRights[BOARD_FILES];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
449 int loadFlag = 0; 
450 int shuffleOpenings;
451 int mute; // mute all sounds
452
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
456 int storedGames = 0;
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
462
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((Boolean annotate));
465 void CleanupTail P((void));
466
467 ChessSquare  FIDEArray[2][BOARD_FILES] = {
468     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471         BlackKing, BlackBishop, BlackKnight, BlackRook }
472 };
473
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackKing, BlackKnight, BlackRook }
479 };
480
481 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484     { BlackRook, BlackMan, BlackBishop, BlackQueen,
485         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
486 };
487
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492         BlackKing, BlackBishop, BlackKnight, BlackRook }
493 };
494
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
500 };
501
502
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
509 };
510
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
516 };
517
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
520         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
522         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
523 };
524
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
527         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
529         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
530 };
531
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
534         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
536         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
537 };
538
539 #ifdef GOTHIC
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
542         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
544         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !GOTHIC
547 #define GothicArray CapablancaArray
548 #endif // !GOTHIC
549
550 #ifdef FALCON
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
553         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
555         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !FALCON
558 #define FalconArray CapablancaArray
559 #endif // !FALCON
560
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
567
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
574 };
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
578
579
580 Board initialPosition;
581
582
583 /* Convert str to a rating. Checks for special cases of "----",
584
585    "++++", etc. Also strips ()'s */
586 int
587 string_to_rating(str)
588   char *str;
589 {
590   while(*str && !isdigit(*str)) ++str;
591   if (!*str)
592     return 0;   /* One of the special "no rating" cases */
593   else
594     return atoi(str);
595 }
596
597 void
598 ClearProgramStats()
599 {
600     /* Init programStats */
601     programStats.movelist[0] = 0;
602     programStats.depth = 0;
603     programStats.nr_moves = 0;
604     programStats.moves_left = 0;
605     programStats.nodes = 0;
606     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
607     programStats.score = 0;
608     programStats.got_only_move = 0;
609     programStats.got_fail = 0;
610     programStats.line_is_book = 0;
611 }
612
613 void
614 InitBackEnd1()
615 {
616     int matched, min, sec;
617
618     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
619
620     GetTimeMark(&programStartTime);
621     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
622
623     ClearProgramStats();
624     programStats.ok_to_send = 1;
625     programStats.seen_stat = 0;
626
627     /*
628      * Initialize game list
629      */
630     ListNew(&gameList);
631
632
633     /*
634      * Internet chess server status
635      */
636     if (appData.icsActive) {
637         appData.matchMode = FALSE;
638         appData.matchGames = 0;
639 #if ZIPPY       
640         appData.noChessProgram = !appData.zippyPlay;
641 #else
642         appData.zippyPlay = FALSE;
643         appData.zippyTalk = FALSE;
644         appData.noChessProgram = TRUE;
645 #endif
646         if (*appData.icsHelper != NULLCHAR) {
647             appData.useTelnet = TRUE;
648             appData.telnetProgram = appData.icsHelper;
649         }
650     } else {
651         appData.zippyTalk = appData.zippyPlay = FALSE;
652     }
653
654     /* [AS] Initialize pv info list [HGM] and game state */
655     {
656         int i, j;
657
658         for( i=0; i<=framePtr; i++ ) {
659             pvInfoList[i].depth = -1;
660             boards[i][EP_STATUS] = EP_NONE;
661             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
662         }
663     }
664
665     /*
666      * Parse timeControl resource
667      */
668     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669                           appData.movesPerSession)) {
670         char buf[MSG_SIZ];
671         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672         DisplayFatalError(buf, 0, 2);
673     }
674
675     /*
676      * Parse searchTime resource
677      */
678     if (*appData.searchTime != NULLCHAR) {
679         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
680         if (matched == 1) {
681             searchTime = min * 60;
682         } else if (matched == 2) {
683             searchTime = min * 60 + sec;
684         } else {
685             char buf[MSG_SIZ];
686             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687             DisplayFatalError(buf, 0, 2);
688         }
689     }
690
691     /* [AS] Adjudication threshold */
692     adjudicateLossThreshold = appData.adjudicateLossThreshold;
693     
694     first.which = "first";
695     second.which = "second";
696     first.maybeThinking = second.maybeThinking = FALSE;
697     first.pr = second.pr = NoProc;
698     first.isr = second.isr = NULL;
699     first.sendTime = second.sendTime = 2;
700     first.sendDrawOffers = 1;
701     if (appData.firstPlaysBlack) {
702         first.twoMachinesColor = "black\n";
703         second.twoMachinesColor = "white\n";
704     } else {
705         first.twoMachinesColor = "white\n";
706         second.twoMachinesColor = "black\n";
707     }
708     first.program = appData.firstChessProgram;
709     second.program = appData.secondChessProgram;
710     first.host = appData.firstHost;
711     second.host = appData.secondHost;
712     first.dir = appData.firstDirectory;
713     second.dir = appData.secondDirectory;
714     first.other = &second;
715     second.other = &first;
716     first.initString = appData.initString;
717     second.initString = appData.secondInitString;
718     first.computerString = appData.firstComputerString;
719     second.computerString = appData.secondComputerString;
720     first.useSigint = second.useSigint = TRUE;
721     first.useSigterm = second.useSigterm = TRUE;
722     first.reuse = appData.reuseFirst;
723     second.reuse = appData.reuseSecond;
724     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
725     second.nps = appData.secondNPS;
726     first.useSetboard = second.useSetboard = FALSE;
727     first.useSAN = second.useSAN = FALSE;
728     first.usePing = second.usePing = FALSE;
729     first.lastPing = second.lastPing = 0;
730     first.lastPong = second.lastPong = 0;
731     first.usePlayother = second.usePlayother = FALSE;
732     first.useColors = second.useColors = TRUE;
733     first.useUsermove = second.useUsermove = FALSE;
734     first.sendICS = second.sendICS = FALSE;
735     first.sendName = second.sendName = appData.icsActive;
736     first.sdKludge = second.sdKludge = FALSE;
737     first.stKludge = second.stKludge = FALSE;
738     TidyProgramName(first.program, first.host, first.tidy);
739     TidyProgramName(second.program, second.host, second.tidy);
740     first.matchWins = second.matchWins = 0;
741     strcpy(first.variants, appData.variant);
742     strcpy(second.variants, appData.variant);
743     first.analysisSupport = second.analysisSupport = 2; /* detect */
744     first.analyzing = second.analyzing = FALSE;
745     first.initDone = second.initDone = FALSE;
746
747     /* New features added by Tord: */
748     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750     /* End of new features added by Tord. */
751     first.fenOverride  = appData.fenOverride1;
752     second.fenOverride = appData.fenOverride2;
753
754     /* [HGM] time odds: set factor for each machine */
755     first.timeOdds  = appData.firstTimeOdds;
756     second.timeOdds = appData.secondTimeOdds;
757     { int norm = 1;
758         if(appData.timeOddsMode) {
759             norm = first.timeOdds;
760             if(norm > second.timeOdds) norm = second.timeOdds;
761         }
762         first.timeOdds /= norm;
763         second.timeOdds /= norm;
764     }
765
766     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767     first.accumulateTC = appData.firstAccumulateTC;
768     second.accumulateTC = appData.secondAccumulateTC;
769     first.maxNrOfSessions = second.maxNrOfSessions = 1;
770
771     /* [HGM] debug */
772     first.debug = second.debug = FALSE;
773     first.supportsNPS = second.supportsNPS = UNKNOWN;
774
775     /* [HGM] options */
776     first.optionSettings  = appData.firstOptions;
777     second.optionSettings = appData.secondOptions;
778
779     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781     first.isUCI = appData.firstIsUCI; /* [AS] */
782     second.isUCI = appData.secondIsUCI; /* [AS] */
783     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
785
786     if (appData.firstProtocolVersion > PROTOVER ||
787         appData.firstProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.firstProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       first.protocolVersion = appData.firstProtocolVersion;
794     }
795
796     if (appData.secondProtocolVersion > PROTOVER ||
797         appData.secondProtocolVersion < 1) {
798       char buf[MSG_SIZ];
799       sprintf(buf, _("protocol version %d not supported"),
800               appData.secondProtocolVersion);
801       DisplayFatalError(buf, 0, 2);
802     } else {
803       second.protocolVersion = appData.secondProtocolVersion;
804     }
805
806     if (appData.icsActive) {
807         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
808 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810         appData.clockMode = FALSE;
811         first.sendTime = second.sendTime = 0;
812     }
813     
814 #if ZIPPY
815     /* Override some settings from environment variables, for backward
816        compatibility.  Unfortunately it's not feasible to have the env
817        vars just set defaults, at least in xboard.  Ugh.
818     */
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
820       ZippyInit();
821     }
822 #endif
823     
824     if (appData.noChessProgram) {
825         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826         sprintf(programVersion, "%s", PACKAGE_STRING);
827     } else {
828       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
831     }
832
833     if (!appData.icsActive) {
834       char buf[MSG_SIZ];
835       /* Check for variants that are supported only in ICS mode,
836          or not at all.  Some that are accepted here nevertheless
837          have bugs; see comments below.
838       */
839       VariantClass variant = StringToVariant(appData.variant);
840       switch (variant) {
841       case VariantBughouse:     /* need four players and two boards */
842       case VariantKriegspiel:   /* need to hide pieces and move details */
843       /* case VariantFischeRandom: (Fabien: moved below) */
844         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845         DisplayFatalError(buf, 0, 2);
846         return;
847
848       case VariantUnknown:
849       case VariantLoadable:
850       case Variant29:
851       case Variant30:
852       case Variant31:
853       case Variant32:
854       case Variant33:
855       case Variant34:
856       case Variant35:
857       case Variant36:
858       default:
859         sprintf(buf, _("Unknown variant name %s"), appData.variant);
860         DisplayFatalError(buf, 0, 2);
861         return;
862
863       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
864       case VariantFairy:      /* [HGM] TestLegality definitely off! */
865       case VariantGothic:     /* [HGM] should work */
866       case VariantCapablanca: /* [HGM] should work */
867       case VariantCourier:    /* [HGM] initial forced moves not implemented */
868       case VariantShogi:      /* [HGM] drops not tested for legality */
869       case VariantKnightmate: /* [HGM] should work */
870       case VariantCylinder:   /* [HGM] untested */
871       case VariantFalcon:     /* [HGM] untested */
872       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873                                  offboard interposition not understood */
874       case VariantNormal:     /* definitely works! */
875       case VariantWildCastle: /* pieces not automatically shuffled */
876       case VariantNoCastle:   /* pieces not automatically shuffled */
877       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878       case VariantLosers:     /* should work except for win condition,
879                                  and doesn't know captures are mandatory */
880       case VariantSuicide:    /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantGiveaway:   /* should work except for win condition,
883                                  and doesn't know captures are mandatory */
884       case VariantTwoKings:   /* should work */
885       case VariantAtomic:     /* should work except for win condition */
886       case Variant3Check:     /* should work except for win condition */
887       case VariantShatranj:   /* should work except for all win conditions */
888       case VariantBerolina:   /* might work if TestLegality is off */
889       case VariantCapaRandom: /* should work */
890       case VariantJanus:      /* should work */
891       case VariantSuper:      /* experimental */
892       case VariantGreat:      /* experimental, requires legality testing to be off */
893         break;
894       }
895     }
896
897     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
898     InitEngineUCI( installDir, &second );
899 }
900
901 int NextIntegerFromString( char ** str, long * value )
902 {
903     int result = -1;
904     char * s = *str;
905
906     while( *s == ' ' || *s == '\t' ) {
907         s++;
908     }
909
910     *value = 0;
911
912     if( *s >= '0' && *s <= '9' ) {
913         while( *s >= '0' && *s <= '9' ) {
914             *value = *value * 10 + (*s - '0');
915             s++;
916         }
917
918         result = 0;
919     }
920
921     *str = s;
922
923     return result;
924 }
925
926 int NextTimeControlFromString( char ** str, long * value )
927 {
928     long temp;
929     int result = NextIntegerFromString( str, &temp );
930
931     if( result == 0 ) {
932         *value = temp * 60; /* Minutes */
933         if( **str == ':' ) {
934             (*str)++;
935             result = NextIntegerFromString( str, &temp );
936             *value += temp; /* Seconds */
937         }
938     }
939
940     return result;
941 }
942
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
945     int result = -1; long temp, temp2;
946
947     if(**str != '+') return -1; // old params remain in force!
948     (*str)++;
949     if( NextTimeControlFromString( str, &temp ) ) return -1;
950
951     if(**str != '/') {
952         /* time only: incremental or sudden-death time control */
953         if(**str == '+') { /* increment follows; read it */
954             (*str)++;
955             if(result = NextIntegerFromString( str, &temp2)) return -1;
956             *inc = temp2 * 1000;
957         } else *inc = 0;
958         *moves = 0; *tc = temp * 1000; 
959         return 0;
960     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
961
962     (*str)++; /* classical time control */
963     result = NextTimeControlFromString( str, &temp2);
964     if(result == 0) {
965         *moves = temp/60;
966         *tc    = temp2 * 1000;
967         *inc   = 0;
968     }
969     return result;
970 }
971
972 int GetTimeQuota(int movenr)
973 {   /* [HGM] get time to add from the multi-session time-control string */
974     int moves=1; /* kludge to force reading of first session */
975     long time, increment;
976     char *s = fullTimeControlString;
977
978     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
979     do {
980         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982         if(movenr == -1) return time;    /* last move before new session     */
983         if(!moves) return increment;     /* current session is incremental   */
984         if(movenr >= 0) movenr -= moves; /* we already finished this session */
985     } while(movenr >= -1);               /* try again for next session       */
986
987     return 0; // no new time quota on this move
988 }
989
990 int
991 ParseTimeControl(tc, ti, mps)
992      char *tc;
993      int ti;
994      int mps;
995 {
996   long tc1;
997   long tc2;
998   char buf[MSG_SIZ];
999   
1000   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1001   if(ti > 0) {
1002     if(mps)
1003       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004     else sprintf(buf, "+%s+%d", tc, ti);
1005   } else {
1006     if(mps)
1007              sprintf(buf, "+%d/%s", mps, tc);
1008     else sprintf(buf, "+%s", tc);
1009   }
1010   fullTimeControlString = StrSave(buf);
1011   
1012   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1013     return FALSE;
1014   }
1015   
1016   if( *tc == '/' ) {
1017     /* Parse second time control */
1018     tc++;
1019     
1020     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1021       return FALSE;
1022     }
1023     
1024     if( tc2 == 0 ) {
1025       return FALSE;
1026     }
1027     
1028     timeControl_2 = tc2 * 1000;
1029   }
1030   else {
1031     timeControl_2 = 0;
1032   }
1033   
1034   if( tc1 == 0 ) {
1035     return FALSE;
1036   }
1037   
1038   timeControl = tc1 * 1000;
1039   
1040   if (ti >= 0) {
1041     timeIncrement = ti * 1000;  /* convert to ms */
1042     movesPerSession = 0;
1043   } else {
1044     timeIncrement = 0;
1045     movesPerSession = mps;
1046   }
1047   return TRUE;
1048 }
1049
1050 void
1051 InitBackEnd2()
1052 {
1053     if (appData.debugMode) {
1054         fprintf(debugFP, "%s\n", programVersion);
1055     }
1056
1057     set_cont_sequence(appData.wrapContSeq);
1058     if (appData.matchGames > 0) {
1059         appData.matchMode = TRUE;
1060     } else if (appData.matchMode) {
1061         appData.matchGames = 1;
1062     }
1063     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064         appData.matchGames = appData.sameColorGames;
1065     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1068     }
1069     Reset(TRUE, FALSE);
1070     if (appData.noChessProgram || first.protocolVersion == 1) {
1071       InitBackEnd3();
1072     } else {
1073       /* kludge: allow timeout for initial "feature" commands */
1074       FreezeUI();
1075       DisplayMessage("", _("Starting chess program"));
1076       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1077     }
1078 }
1079
1080 void
1081 InitBackEnd3 P((void))
1082 {
1083     GameMode initialMode;
1084     char buf[MSG_SIZ];
1085     int err;
1086
1087     InitChessProgram(&first, startedFromSetupPosition);
1088
1089
1090     if (appData.icsActive) {
1091 #ifdef WIN32
1092         /* [DM] Make a console window if needed [HGM] merged ifs */
1093         ConsoleCreate(); 
1094 #endif
1095         err = establish();
1096         if (err != 0) {
1097             if (*appData.icsCommPort != NULLCHAR) {
1098                 sprintf(buf, _("Could not open comm port %s"),  
1099                         appData.icsCommPort);
1100             } else {
1101                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1102                         appData.icsHost, appData.icsPort);
1103             }
1104             DisplayFatalError(buf, err, 1);
1105             return;
1106         }
1107         SetICSMode();
1108         telnetISR =
1109           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1110         fromUserISR =
1111           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112     } else if (appData.noChessProgram) {
1113         SetNCPMode();
1114     } else {
1115         SetGNUMode();
1116     }
1117
1118     if (*appData.cmailGameName != NULLCHAR) {
1119         SetCmailMode();
1120         OpenLoopback(&cmailPR);
1121         cmailISR =
1122           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1123     }
1124     
1125     ThawUI();
1126     DisplayMessage("", "");
1127     if (StrCaseCmp(appData.initialMode, "") == 0) {
1128       initialMode = BeginningOfGame;
1129     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130       initialMode = TwoMachinesPlay;
1131     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132       initialMode = AnalyzeFile; 
1133     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134       initialMode = AnalyzeMode;
1135     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136       initialMode = MachinePlaysWhite;
1137     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138       initialMode = MachinePlaysBlack;
1139     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140       initialMode = EditGame;
1141     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142       initialMode = EditPosition;
1143     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144       initialMode = Training;
1145     } else {
1146       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147       DisplayFatalError(buf, 0, 2);
1148       return;
1149     }
1150
1151     if (appData.matchMode) {
1152         /* Set up machine vs. machine match */
1153         if (appData.noChessProgram) {
1154             DisplayFatalError(_("Can't have a match with no chess programs"),
1155                               0, 2);
1156             return;
1157         }
1158         matchMode = TRUE;
1159         matchGame = 1;
1160         if (*appData.loadGameFile != NULLCHAR) {
1161             int index = appData.loadGameIndex; // [HGM] autoinc
1162             if(index<0) lastIndex = index = 1;
1163             if (!LoadGameFromFile(appData.loadGameFile,
1164                                   index,
1165                                   appData.loadGameFile, FALSE)) {
1166                 DisplayFatalError(_("Bad game file"), 0, 1);
1167                 return;
1168             }
1169         } else if (*appData.loadPositionFile != NULLCHAR) {
1170             int index = appData.loadPositionIndex; // [HGM] autoinc
1171             if(index<0) lastIndex = index = 1;
1172             if (!LoadPositionFromFile(appData.loadPositionFile,
1173                                       index,
1174                                       appData.loadPositionFile)) {
1175                 DisplayFatalError(_("Bad position file"), 0, 1);
1176                 return;
1177             }
1178         }
1179         TwoMachinesEvent();
1180     } else if (*appData.cmailGameName != NULLCHAR) {
1181         /* Set up cmail mode */
1182         ReloadCmailMsgEvent(TRUE);
1183     } else {
1184         /* Set up other modes */
1185         if (initialMode == AnalyzeFile) {
1186           if (*appData.loadGameFile == NULLCHAR) {
1187             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1188             return;
1189           }
1190         }
1191         if (*appData.loadGameFile != NULLCHAR) {
1192             (void) LoadGameFromFile(appData.loadGameFile,
1193                                     appData.loadGameIndex,
1194                                     appData.loadGameFile, TRUE);
1195         } else if (*appData.loadPositionFile != NULLCHAR) {
1196             (void) LoadPositionFromFile(appData.loadPositionFile,
1197                                         appData.loadPositionIndex,
1198                                         appData.loadPositionFile);
1199             /* [HGM] try to make self-starting even after FEN load */
1200             /* to allow automatic setup of fairy variants with wtm */
1201             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202                 gameMode = BeginningOfGame;
1203                 setboardSpoiledMachineBlack = 1;
1204             }
1205             /* [HGM] loadPos: make that every new game uses the setup */
1206             /* from file as long as we do not switch variant          */
1207             if(!blackPlaysFirst) {
1208                 startedFromPositionFile = TRUE;
1209                 CopyBoard(filePosition, boards[0]);
1210             }
1211         }
1212         if (initialMode == AnalyzeMode) {
1213           if (appData.noChessProgram) {
1214             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1215             return;
1216           }
1217           if (appData.icsActive) {
1218             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1219             return;
1220           }
1221           AnalyzeModeEvent();
1222         } else if (initialMode == AnalyzeFile) {
1223           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224           ShowThinkingEvent();
1225           AnalyzeFileEvent();
1226           AnalysisPeriodicEvent(1);
1227         } else if (initialMode == MachinePlaysWhite) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineWhiteEvent();
1239         } else if (initialMode == MachinePlaysBlack) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           MachineBlackEvent();
1251         } else if (initialMode == TwoMachinesPlay) {
1252           if (appData.noChessProgram) {
1253             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1254                               0, 2);
1255             return;
1256           }
1257           if (appData.icsActive) {
1258             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1259                               0, 2);
1260             return;
1261           }
1262           TwoMachinesEvent();
1263         } else if (initialMode == EditGame) {
1264           EditGameEvent();
1265         } else if (initialMode == EditPosition) {
1266           EditPositionEvent();
1267         } else if (initialMode == Training) {
1268           if (*appData.loadGameFile == NULLCHAR) {
1269             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1270             return;
1271           }
1272           TrainingEvent();
1273         }
1274     }
1275 }
1276
1277 /*
1278  * Establish will establish a contact to a remote host.port.
1279  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280  *  used to talk to the host.
1281  * Returns 0 if okay, error code if not.
1282  */
1283 int
1284 establish()
1285 {
1286     char buf[MSG_SIZ];
1287
1288     if (*appData.icsCommPort != NULLCHAR) {
1289         /* Talk to the host through a serial comm port */
1290         return OpenCommPort(appData.icsCommPort, &icsPR);
1291
1292     } else if (*appData.gateway != NULLCHAR) {
1293         if (*appData.remoteShell == NULLCHAR) {
1294             /* Use the rcmd protocol to run telnet program on a gateway host */
1295             snprintf(buf, sizeof(buf), "%s %s %s",
1296                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1297             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1298
1299         } else {
1300             /* Use the rsh program to run telnet program on a gateway host */
1301             if (*appData.remoteUser == NULLCHAR) {
1302                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303                         appData.gateway, appData.telnetProgram,
1304                         appData.icsHost, appData.icsPort);
1305             } else {
1306                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307                         appData.remoteShell, appData.gateway, 
1308                         appData.remoteUser, appData.telnetProgram,
1309                         appData.icsHost, appData.icsPort);
1310             }
1311             return StartChildProcess(buf, "", &icsPR);
1312
1313         }
1314     } else if (appData.useTelnet) {
1315         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1316
1317     } else {
1318         /* TCP socket interface differs somewhat between
1319            Unix and NT; handle details in the front end.
1320            */
1321         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1322     }
1323 }
1324
1325 void
1326 show_bytes(fp, buf, count)
1327      FILE *fp;
1328      char *buf;
1329      int count;
1330 {
1331     while (count--) {
1332         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333             fprintf(fp, "\\%03o", *buf & 0xff);
1334         } else {
1335             putc(*buf, fp);
1336         }
1337         buf++;
1338     }
1339     fflush(fp);
1340 }
1341
1342 /* Returns an errno value */
1343 int
1344 OutputMaybeTelnet(pr, message, count, outError)
1345      ProcRef pr;
1346      char *message;
1347      int count;
1348      int *outError;
1349 {
1350     char buf[8192], *p, *q, *buflim;
1351     int left, newcount, outcount;
1352
1353     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354         *appData.gateway != NULLCHAR) {
1355         if (appData.debugMode) {
1356             fprintf(debugFP, ">ICS: ");
1357             show_bytes(debugFP, message, count);
1358             fprintf(debugFP, "\n");
1359         }
1360         return OutputToProcess(pr, message, count, outError);
1361     }
1362
1363     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1364     p = message;
1365     q = buf;
1366     left = count;
1367     newcount = 0;
1368     while (left) {
1369         if (q >= buflim) {
1370             if (appData.debugMode) {
1371                 fprintf(debugFP, ">ICS: ");
1372                 show_bytes(debugFP, buf, newcount);
1373                 fprintf(debugFP, "\n");
1374             }
1375             outcount = OutputToProcess(pr, buf, newcount, outError);
1376             if (outcount < newcount) return -1; /* to be sure */
1377             q = buf;
1378             newcount = 0;
1379         }
1380         if (*p == '\n') {
1381             *q++ = '\r';
1382             newcount++;
1383         } else if (((unsigned char) *p) == TN_IAC) {
1384             *q++ = (char) TN_IAC;
1385             newcount ++;
1386         }
1387         *q++ = *p++;
1388         newcount++;
1389         left--;
1390     }
1391     if (appData.debugMode) {
1392         fprintf(debugFP, ">ICS: ");
1393         show_bytes(debugFP, buf, newcount);
1394         fprintf(debugFP, "\n");
1395     }
1396     outcount = OutputToProcess(pr, buf, newcount, outError);
1397     if (outcount < newcount) return -1; /* to be sure */
1398     return count;
1399 }
1400
1401 void
1402 read_from_player(isr, closure, message, count, error)
1403      InputSourceRef isr;
1404      VOIDSTAR closure;
1405      char *message;
1406      int count;
1407      int error;
1408 {
1409     int outError, outCount;
1410     static int gotEof = 0;
1411
1412     /* Pass data read from player on to ICS */
1413     if (count > 0) {
1414         gotEof = 0;
1415         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416         if (outCount < count) {
1417             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1418         }
1419     } else if (count < 0) {
1420         RemoveInputSource(isr);
1421         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422     } else if (gotEof++ > 0) {
1423         RemoveInputSource(isr);
1424         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1425     }
1426 }
1427
1428 void
1429 KeepAlive()
1430 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431     SendToICS("date\n");
1432     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1433 }
1434
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1437 {
1438     char buffer[MSG_SIZ];
1439     va_list args;
1440
1441     va_start(args, format);
1442     vsnprintf(buffer, sizeof(buffer), format, args);
1443     buffer[sizeof(buffer)-1] = '\0';
1444     SendToICS(buffer);
1445     va_end(args);
1446 }
1447
1448 void
1449 SendToICS(s)
1450      char *s;
1451 {
1452     int count, outCount, outError;
1453
1454     if (icsPR == NULL) return;
1455
1456     count = strlen(s);
1457     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458     if (outCount < count) {
1459         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1460     }
1461 }
1462
1463 /* This is used for sending logon scripts to the ICS. Sending
1464    without a delay causes problems when using timestamp on ICC
1465    (at least on my machine). */
1466 void
1467 SendToICSDelayed(s,msdelay)
1468      char *s;
1469      long msdelay;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     if (appData.debugMode) {
1477         fprintf(debugFP, ">ICS: ");
1478         show_bytes(debugFP, s, count);
1479         fprintf(debugFP, "\n");
1480     }
1481     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1482                                       msdelay);
1483     if (outCount < count) {
1484         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1485     }
1486 }
1487
1488
1489 /* Remove all highlighting escape sequences in s
1490    Also deletes any suffix starting with '(' 
1491    */
1492 char *
1493 StripHighlightAndTitle(s)
1494      char *s;
1495 {
1496     static char retbuf[MSG_SIZ];
1497     char *p = retbuf;
1498
1499     while (*s != NULLCHAR) {
1500         while (*s == '\033') {
1501             while (*s != NULLCHAR && !isalpha(*s)) s++;
1502             if (*s != NULLCHAR) s++;
1503         }
1504         while (*s != NULLCHAR && *s != '\033') {
1505             if (*s == '(' || *s == '[') {
1506                 *p = NULLCHAR;
1507                 return retbuf;
1508             }
1509             *p++ = *s++;
1510         }
1511     }
1512     *p = NULLCHAR;
1513     return retbuf;
1514 }
1515
1516 /* Remove all highlighting escape sequences in s */
1517 char *
1518 StripHighlight(s)
1519      char *s;
1520 {
1521     static char retbuf[MSG_SIZ];
1522     char *p = retbuf;
1523
1524     while (*s != NULLCHAR) {
1525         while (*s == '\033') {
1526             while (*s != NULLCHAR && !isalpha(*s)) s++;
1527             if (*s != NULLCHAR) s++;
1528         }
1529         while (*s != NULLCHAR && *s != '\033') {
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 char *variantNames[] = VARIANT_NAMES;
1538 char *
1539 VariantName(v)
1540      VariantClass v;
1541 {
1542     return variantNames[v];
1543 }
1544
1545
1546 /* Identify a variant from the strings the chess servers use or the
1547    PGN Variant tag names we use. */
1548 VariantClass
1549 StringToVariant(e)
1550      char *e;
1551 {
1552     char *p;
1553     int wnum = -1;
1554     VariantClass v = VariantNormal;
1555     int i, found = FALSE;
1556     char buf[MSG_SIZ];
1557
1558     if (!e) return v;
1559
1560     /* [HGM] skip over optional board-size prefixes */
1561     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563         while( *e++ != '_');
1564     }
1565
1566     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1567         v = VariantNormal;
1568         found = TRUE;
1569     } else
1570     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571       if (StrCaseStr(e, variantNames[i])) {
1572         v = (VariantClass) i;
1573         found = TRUE;
1574         break;
1575       }
1576     }
1577
1578     if (!found) {
1579       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580           || StrCaseStr(e, "wild/fr") 
1581           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582         v = VariantFischeRandom;
1583       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584                  (i = 1, p = StrCaseStr(e, "w"))) {
1585         p += i;
1586         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1587         if (isdigit(*p)) {
1588           wnum = atoi(p);
1589         } else {
1590           wnum = -1;
1591         }
1592         switch (wnum) {
1593         case 0: /* FICS only, actually */
1594         case 1:
1595           /* Castling legal even if K starts on d-file */
1596           v = VariantWildCastle;
1597           break;
1598         case 2:
1599         case 3:
1600         case 4:
1601           /* Castling illegal even if K & R happen to start in
1602              normal positions. */
1603           v = VariantNoCastle;
1604           break;
1605         case 5:
1606         case 7:
1607         case 8:
1608         case 10:
1609         case 11:
1610         case 12:
1611         case 13:
1612         case 14:
1613         case 15:
1614         case 18:
1615         case 19:
1616           /* Castling legal iff K & R start in normal positions */
1617           v = VariantNormal;
1618           break;
1619         case 6:
1620         case 20:
1621         case 21:
1622           /* Special wilds for position setup; unclear what to do here */
1623           v = VariantLoadable;
1624           break;
1625         case 9:
1626           /* Bizarre ICC game */
1627           v = VariantTwoKings;
1628           break;
1629         case 16:
1630           v = VariantKriegspiel;
1631           break;
1632         case 17:
1633           v = VariantLosers;
1634           break;
1635         case 22:
1636           v = VariantFischeRandom;
1637           break;
1638         case 23:
1639           v = VariantCrazyhouse;
1640           break;
1641         case 24:
1642           v = VariantBughouse;
1643           break;
1644         case 25:
1645           v = Variant3Check;
1646           break;
1647         case 26:
1648           /* Not quite the same as FICS suicide! */
1649           v = VariantGiveaway;
1650           break;
1651         case 27:
1652           v = VariantAtomic;
1653           break;
1654         case 28:
1655           v = VariantShatranj;
1656           break;
1657
1658         /* Temporary names for future ICC types.  The name *will* change in 
1659            the next xboard/WinBoard release after ICC defines it. */
1660         case 29:
1661           v = Variant29;
1662           break;
1663         case 30:
1664           v = Variant30;
1665           break;
1666         case 31:
1667           v = Variant31;
1668           break;
1669         case 32:
1670           v = Variant32;
1671           break;
1672         case 33:
1673           v = Variant33;
1674           break;
1675         case 34:
1676           v = Variant34;
1677           break;
1678         case 35:
1679           v = Variant35;
1680           break;
1681         case 36:
1682           v = Variant36;
1683           break;
1684         case 37:
1685           v = VariantShogi;
1686           break;
1687         case 38:
1688           v = VariantXiangqi;
1689           break;
1690         case 39:
1691           v = VariantCourier;
1692           break;
1693         case 40:
1694           v = VariantGothic;
1695           break;
1696         case 41:
1697           v = VariantCapablanca;
1698           break;
1699         case 42:
1700           v = VariantKnightmate;
1701           break;
1702         case 43:
1703           v = VariantFairy;
1704           break;
1705         case 44:
1706           v = VariantCylinder;
1707           break;
1708         case 45:
1709           v = VariantFalcon;
1710           break;
1711         case 46:
1712           v = VariantCapaRandom;
1713           break;
1714         case 47:
1715           v = VariantBerolina;
1716           break;
1717         case 48:
1718           v = VariantJanus;
1719           break;
1720         case 49:
1721           v = VariantSuper;
1722           break;
1723         case 50:
1724           v = VariantGreat;
1725           break;
1726         case -1:
1727           /* Found "wild" or "w" in the string but no number;
1728              must assume it's normal chess. */
1729           v = VariantNormal;
1730           break;
1731         default:
1732           sprintf(buf, _("Unknown wild type %d"), wnum);
1733           DisplayError(buf, 0);
1734           v = VariantUnknown;
1735           break;
1736         }
1737       }
1738     }
1739     if (appData.debugMode) {
1740       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741               e, wnum, VariantName(v));
1742     }
1743     return v;
1744 }
1745
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1748
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750    advance *index beyond it, and set leftover_start to the new value of
1751    *index; else return FALSE.  If pattern contains the character '*', it
1752    matches any sequence of characters not containing '\r', '\n', or the
1753    character following the '*' (if any), and the matched sequence(s) are
1754    copied into star_match.
1755    */
1756 int
1757 looking_at(buf, index, pattern)
1758      char *buf;
1759      int *index;
1760      char *pattern;
1761 {
1762     char *bufp = &buf[*index], *patternp = pattern;
1763     int star_count = 0;
1764     char *matchp = star_match[0];
1765     
1766     for (;;) {
1767         if (*patternp == NULLCHAR) {
1768             *index = leftover_start = bufp - buf;
1769             *matchp = NULLCHAR;
1770             return TRUE;
1771         }
1772         if (*bufp == NULLCHAR) return FALSE;
1773         if (*patternp == '*') {
1774             if (*bufp == *(patternp + 1)) {
1775                 *matchp = NULLCHAR;
1776                 matchp = star_match[++star_count];
1777                 patternp += 2;
1778                 bufp++;
1779                 continue;
1780             } else if (*bufp == '\n' || *bufp == '\r') {
1781                 patternp++;
1782                 if (*patternp == NULLCHAR)
1783                   continue;
1784                 else
1785                   return FALSE;
1786             } else {
1787                 *matchp++ = *bufp++;
1788                 continue;
1789             }
1790         }
1791         if (*patternp != *bufp) return FALSE;
1792         patternp++;
1793         bufp++;
1794     }
1795 }
1796
1797 void
1798 SendToPlayer(data, length)
1799      char *data;
1800      int length;
1801 {
1802     int error, outCount;
1803     outCount = OutputToProcess(NoProc, data, length, &error);
1804     if (outCount < length) {
1805         DisplayFatalError(_("Error writing to display"), error, 1);
1806     }
1807 }
1808
1809 void
1810 PackHolding(packed, holding)
1811      char packed[];
1812      char *holding;
1813 {
1814     char *p = holding;
1815     char *q = packed;
1816     int runlength = 0;
1817     int curr = 9999;
1818     do {
1819         if (*p == curr) {
1820             runlength++;
1821         } else {
1822             switch (runlength) {
1823               case 0:
1824                 break;
1825               case 1:
1826                 *q++ = curr;
1827                 break;
1828               case 2:
1829                 *q++ = curr;
1830                 *q++ = curr;
1831                 break;
1832               default:
1833                 sprintf(q, "%d", runlength);
1834                 while (*q) q++;
1835                 *q++ = curr;
1836                 break;
1837             }
1838             runlength = 1;
1839             curr = *p;
1840         }
1841     } while (*p++);
1842     *q = NULLCHAR;
1843 }
1844
1845 /* Telnet protocol requests from the front end */
1846 void
1847 TelnetRequest(ddww, option)
1848      unsigned char ddww, option;
1849 {
1850     unsigned char msg[3];
1851     int outCount, outError;
1852
1853     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1854
1855     if (appData.debugMode) {
1856         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1857         switch (ddww) {
1858           case TN_DO:
1859             ddwwStr = "DO";
1860             break;
1861           case TN_DONT:
1862             ddwwStr = "DONT";
1863             break;
1864           case TN_WILL:
1865             ddwwStr = "WILL";
1866             break;
1867           case TN_WONT:
1868             ddwwStr = "WONT";
1869             break;
1870           default:
1871             ddwwStr = buf1;
1872             sprintf(buf1, "%d", ddww);
1873             break;
1874         }
1875         switch (option) {
1876           case TN_ECHO:
1877             optionStr = "ECHO";
1878             break;
1879           default:
1880             optionStr = buf2;
1881             sprintf(buf2, "%d", option);
1882             break;
1883         }
1884         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1885     }
1886     msg[0] = TN_IAC;
1887     msg[1] = ddww;
1888     msg[2] = option;
1889     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1890     if (outCount < 3) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895 void
1896 DoEcho()
1897 {
1898     if (!appData.icsActive) return;
1899     TelnetRequest(TN_DO, TN_ECHO);
1900 }
1901
1902 void
1903 DontEcho()
1904 {
1905     if (!appData.icsActive) return;
1906     TelnetRequest(TN_DONT, TN_ECHO);
1907 }
1908
1909 void
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1911 {
1912     /* put the holdings sent to us by the server on the board holdings area */
1913     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1914     char p;
1915     ChessSquare piece;
1916
1917     if(gameInfo.holdingsWidth < 2)  return;
1918     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919         return; // prevent overwriting by pre-board holdings
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948 }
1949
1950
1951 void
1952 VariantSwitch(Board board, VariantClass newVariant)
1953 {
1954    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1955    Board oldBoard;
1956
1957    startedFromPositionFile = FALSE;
1958    if(gameInfo.variant == newVariant) return;
1959
1960    /* [HGM] This routine is called each time an assignment is made to
1961     * gameInfo.variant during a game, to make sure the board sizes
1962     * are set to match the new variant. If that means adding or deleting
1963     * holdings, we shift the playing board accordingly
1964     * This kludge is needed because in ICS observe mode, we get boards
1965     * of an ongoing game without knowing the variant, and learn about the
1966     * latter only later. This can be because of the move list we requested,
1967     * in which case the game history is refilled from the beginning anyway,
1968     * but also when receiving holdings of a crazyhouse game. In the latter
1969     * case we want to add those holdings to the already received position.
1970     */
1971
1972    
1973    if (appData.debugMode) {
1974      fprintf(debugFP, "Switch board from %s to %s\n",
1975              VariantName(gameInfo.variant), VariantName(newVariant));
1976      setbuf(debugFP, NULL);
1977    }
1978    shuffleOpenings = 0;       /* [HGM] shuffle */
1979    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1980    switch(newVariant) 
1981      {
1982      case VariantShogi:
1983        newWidth = 9;  newHeight = 9;
1984        gameInfo.holdingsSize = 7;
1985      case VariantBughouse:
1986      case VariantCrazyhouse:
1987        newHoldingsWidth = 2; break;
1988      case VariantGreat:
1989        newWidth = 10;
1990      case VariantSuper:
1991        newHoldingsWidth = 2;
1992        gameInfo.holdingsSize = 8;
1993        break;
1994      case VariantGothic:
1995      case VariantCapablanca:
1996      case VariantCapaRandom:
1997        newWidth = 10;
1998      default:
1999        newHoldingsWidth = gameInfo.holdingsSize = 0;
2000      };
2001    
2002    if(newWidth  != gameInfo.boardWidth  ||
2003       newHeight != gameInfo.boardHeight ||
2004       newHoldingsWidth != gameInfo.holdingsWidth ) {
2005      
2006      /* shift position to new playing area, if needed */
2007      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008        for(i=0; i<BOARD_HEIGHT; i++) 
2009          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011              board[i][j];
2012        for(i=0; i<newHeight; i++) {
2013          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015        }
2016      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017        for(i=0; i<BOARD_HEIGHT; i++)
2018          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2020              board[i][j];
2021      }
2022      gameInfo.boardWidth  = newWidth;
2023      gameInfo.boardHeight = newHeight;
2024      gameInfo.holdingsWidth = newHoldingsWidth;
2025      gameInfo.variant = newVariant;
2026      InitDrawingSizes(-2, 0);
2027    } else gameInfo.variant = newVariant;
2028    CopyBoard(oldBoard, board);   // remember correctly formatted board
2029      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2030    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2031 }
2032
2033 static int loggedOn = FALSE;
2034
2035 /*-- Game start info cache: --*/
2036 int gs_gamenum;
2037 char gs_kind[MSG_SIZ];
2038 static char player1Name[128] = "";
2039 static char player2Name[128] = "";
2040 static char cont_seq[] = "\n\\   ";
2041 static int player1Rating = -1;
2042 static int player2Rating = -1;
2043 /*----------------------------*/
2044
2045 ColorClass curColor = ColorNormal;
2046 int suppressKibitz = 0;
2047
2048 void
2049 read_from_ics(isr, closure, data, count, error)
2050      InputSourceRef isr;
2051      VOIDSTAR closure;
2052      char *data;
2053      int count;
2054      int error;
2055 {
2056 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2057 #define STARTED_NONE 0
2058 #define STARTED_MOVES 1
2059 #define STARTED_BOARD 2
2060 #define STARTED_OBSERVE 3
2061 #define STARTED_HOLDINGS 4
2062 #define STARTED_CHATTER 5
2063 #define STARTED_COMMENT 6
2064 #define STARTED_MOVES_NOHIDE 7
2065     
2066     static int started = STARTED_NONE;
2067     static char parse[20000];
2068     static int parse_pos = 0;
2069     static char buf[BUF_SIZE + 1];
2070     static int firstTime = TRUE, intfSet = FALSE;
2071     static ColorClass prevColor = ColorNormal;
2072     static int savingComment = FALSE;
2073     static int cmatch = 0; // continuation sequence match
2074     char *bp;
2075     char str[500];
2076     int i, oldi;
2077     int buf_len;
2078     int next_out;
2079     int tkind;
2080     int backup;    /* [DM] For zippy color lines */
2081     char *p;
2082     char talker[MSG_SIZ]; // [HGM] chat
2083     int channel;
2084
2085     if (appData.debugMode) {
2086       if (!error) {
2087         fprintf(debugFP, "<ICS: ");
2088         show_bytes(debugFP, data, count);
2089         fprintf(debugFP, "\n");
2090       }
2091     }
2092
2093     if (appData.debugMode) { int f = forwardMostMove;
2094         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2095                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2096                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106     /* copy new characters into the buffer */
2107     bp = buf + leftover_len;
2108     buf_len=leftover_len;
2109     for (i=0; i<count; i++)
2110     {
2111         // ignore these
2112         if (data[i] == '\r')
2113             continue;
2114
2115         // join lines split by ICS?
2116         if (!appData.noJoin)
2117         {
2118             /*
2119                 Joining just consists of finding matches against the
2120                 continuation sequence, and discarding that sequence
2121                 if found instead of copying it.  So, until a match
2122                 fails, there's nothing to do since it might be the
2123                 complete sequence, and thus, something we don't want
2124                 copied.
2125             */
2126             if (data[i] == cont_seq[cmatch])
2127             {
2128                 cmatch++;
2129                 if (cmatch == strlen(cont_seq))
2130                 {
2131                     cmatch = 0; // complete match.  just reset the counter
2132
2133                     /*
2134                         it's possible for the ICS to not include the space
2135                         at the end of the last word, making our [correct]
2136                         join operation fuse two separate words.  the server
2137                         does this when the space occurs at the width setting.
2138                     */
2139                     if (!buf_len || buf[buf_len-1] != ' ')
2140                     {
2141                         *bp++ = ' ';
2142                         buf_len++;
2143                     }
2144                 }
2145                 continue;
2146             }
2147             else if (cmatch)
2148             {
2149                 /*
2150                     match failed, so we have to copy what matched before
2151                     falling through and copying this character.  In reality,
2152                     this will only ever be just the newline character, but
2153                     it doesn't hurt to be precise.
2154                 */
2155                 strncpy(bp, cont_seq, cmatch);
2156                 bp += cmatch;
2157                 buf_len += cmatch;
2158                 cmatch = 0;
2159             }
2160         }
2161
2162         // copy this char
2163         *bp++ = data[i];
2164         buf_len++;
2165     }
2166
2167         buf[buf_len] = NULLCHAR;
2168         next_out = leftover_len;
2169         leftover_start = 0;
2170         
2171         i = 0;
2172         while (i < buf_len) {
2173             /* Deal with part of the TELNET option negotiation
2174                protocol.  We refuse to do anything beyond the
2175                defaults, except that we allow the WILL ECHO option,
2176                which ICS uses to turn off password echoing when we are
2177                directly connected to it.  We reject this option
2178                if localLineEditing mode is on (always on in xboard)
2179                and we are talking to port 23, which might be a real
2180                telnet server that will try to keep WILL ECHO on permanently.
2181              */
2182             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2183                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2184                 unsigned char option;
2185                 oldi = i;
2186                 switch ((unsigned char) buf[++i]) {
2187                   case TN_WILL:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<WILL ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       case TN_ECHO:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "ECHO ");
2194                         /* Reply only if this is a change, according
2195                            to the protocol rules. */
2196                         if (remoteEchoOption) break;
2197                         if (appData.localLineEditing &&
2198                             atoi(appData.icsPort) == TN_PORT) {
2199                             TelnetRequest(TN_DONT, TN_ECHO);
2200                         } else {
2201                             EchoOff();
2202                             TelnetRequest(TN_DO, TN_ECHO);
2203                             remoteEchoOption = TRUE;
2204                         }
2205                         break;
2206                       default:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "%d ", option);
2209                         /* Whatever this is, we don't want it. */
2210                         TelnetRequest(TN_DONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_WONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<WONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       case TN_ECHO:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "ECHO ");
2221                         /* Reply only if this is a change, according
2222                            to the protocol rules. */
2223                         if (!remoteEchoOption) break;
2224                         EchoOn();
2225                         TelnetRequest(TN_DONT, TN_ECHO);
2226                         remoteEchoOption = FALSE;
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", (unsigned char) option);
2231                         /* Whatever this is, it must already be turned
2232                            off, because we never agree to turn on
2233                            anything non-default, so according to the
2234                            protocol rules, we don't reply. */
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DO:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DO ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         /* Whatever this is, we refuse to do it. */
2244                         if (appData.debugMode)
2245                           fprintf(debugFP, "%d ", option);
2246                         TelnetRequest(TN_WONT, option);
2247                         break;
2248                     }
2249                     break;
2250                   case TN_DONT:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<DONT ");
2253                     switch (option = (unsigned char) buf[++i]) {
2254                       default:
2255                         if (appData.debugMode)
2256                           fprintf(debugFP, "%d ", option);
2257                         /* Whatever this is, we are already not doing
2258                            it, because we never agree to do anything
2259                            non-default, so according to the protocol
2260                            rules, we don't reply. */
2261                         break;
2262                     }
2263                     break;
2264                   case TN_IAC:
2265                     if (appData.debugMode)
2266                       fprintf(debugFP, "\n<IAC ");
2267                     /* Doubled IAC; pass it through */
2268                     i--;
2269                     break;
2270                   default:
2271                     if (appData.debugMode)
2272                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2273                     /* Drop all other telnet commands on the floor */
2274                     break;
2275                 }
2276                 if (oldi > next_out)
2277                   SendToPlayer(&buf[next_out], oldi - next_out);
2278                 if (++i > next_out)
2279                   next_out = i;
2280                 continue;
2281             }
2282                 
2283             /* OK, this at least will *usually* work */
2284             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2285                 loggedOn = TRUE;
2286             }
2287             
2288             if (loggedOn && !intfSet) {
2289                 if (ics_type == ICS_ICC) {
2290                   sprintf(str,
2291                           "/set-quietly interface %s\n/set-quietly style 12\n",
2292                           programVersion);
2293                 } else if (ics_type == ICS_CHESSNET) {
2294                   sprintf(str, "/style 12\n");
2295                 } else {
2296                   strcpy(str, "alias $ @\n$set interface ");
2297                   strcat(str, programVersion);
2298                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2299 #ifdef WIN32
2300                   strcat(str, "$iset nohighlight 1\n");
2301 #endif
2302                   strcat(str, "$iset lock 1\n$style 12\n");
2303                 }
2304                 SendToICS(str);
2305                 NotifyFrontendLogin();
2306                 intfSet = TRUE;
2307             }
2308
2309             if (started == STARTED_COMMENT) {
2310                 /* Accumulate characters in comment */
2311                 parse[parse_pos++] = buf[i];
2312                 if (buf[i] == '\n') {
2313                     parse[parse_pos] = NULLCHAR;
2314                     if(chattingPartner>=0) {
2315                         char mess[MSG_SIZ];
2316                         sprintf(mess, "%s%s", talker, parse);
2317                         OutputChatMessage(chattingPartner, mess);
2318                         chattingPartner = -1;
2319                     } else
2320                     if(!suppressKibitz) // [HGM] kibitz
2321                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2322                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2323                         int nrDigit = 0, nrAlph = 0, i;
2324                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2325                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2326                         parse[parse_pos] = NULLCHAR;
2327                         // try to be smart: if it does not look like search info, it should go to
2328                         // ICS interaction window after all, not to engine-output window.
2329                         for(i=0; i<parse_pos; i++) { // count letters and digits
2330                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2331                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2332                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2333                         }
2334                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2335                             int depth=0; float score;
2336                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2337                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2338                                 pvInfoList[forwardMostMove-1].depth = depth;
2339                                 pvInfoList[forwardMostMove-1].score = 100*score;
2340                             }
2341                             OutputKibitz(suppressKibitz, parse);
2342                         } else {
2343                             char tmp[MSG_SIZ];
2344                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2345                             SendToPlayer(tmp, strlen(tmp));
2346                         }
2347                     }
2348                     started = STARTED_NONE;
2349                 } else {
2350                     /* Don't match patterns against characters in chatter */
2351                     i++;
2352                     continue;
2353                 }
2354             }
2355             if (started == STARTED_CHATTER) {
2356                 if (buf[i] != '\n') {
2357                     /* Don't match patterns against characters in chatter */
2358                     i++;
2359                     continue;
2360                 }
2361                 started = STARTED_NONE;
2362             }
2363
2364             /* Kludge to deal with rcmd protocol */
2365             if (firstTime && looking_at(buf, &i, "\001*")) {
2366                 DisplayFatalError(&buf[1], 0, 1);
2367                 continue;
2368             } else {
2369                 firstTime = FALSE;
2370             }
2371
2372             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2373                 ics_type = ICS_ICC;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2380                 ics_type = ICS_FICS;
2381                 ics_prefix = "$";
2382                 if (appData.debugMode)
2383                   fprintf(debugFP, "ics_type %d\n", ics_type);
2384                 continue;
2385             }
2386             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2387                 ics_type = ICS_CHESSNET;
2388                 ics_prefix = "/";
2389                 if (appData.debugMode)
2390                   fprintf(debugFP, "ics_type %d\n", ics_type);
2391                 continue;
2392             }
2393
2394             if (!loggedOn &&
2395                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2396                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2397                  looking_at(buf, &i, "will be \"*\""))) {
2398               strcpy(ics_handle, star_match[0]);
2399               continue;
2400             }
2401
2402             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2403               char buf[MSG_SIZ];
2404               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2405               DisplayIcsInteractionTitle(buf);
2406               have_set_title = TRUE;
2407             }
2408
2409             /* skip finger notes */
2410             if (started == STARTED_NONE &&
2411                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2412                  (buf[i] == '1' && buf[i+1] == '0')) &&
2413                 buf[i+2] == ':' && buf[i+3] == ' ') {
2414               started = STARTED_CHATTER;
2415               i += 3;
2416               continue;
2417             }
2418
2419             /* skip formula vars */
2420             if (started == STARTED_NONE &&
2421                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2422               started = STARTED_CHATTER;
2423               i += 3;
2424               continue;
2425             }
2426
2427             oldi = i;
2428             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2429             if (appData.autoKibitz && started == STARTED_NONE && 
2430                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2431                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2432                 if(looking_at(buf, &i, "* kibitzes: ") &&
2433                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2434                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2435                         suppressKibitz = TRUE;
2436                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2437                                 && (gameMode == IcsPlayingWhite)) ||
2438                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2439                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2440                             started = STARTED_CHATTER; // own kibitz we simply discard
2441                         else {
2442                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2443                             parse_pos = 0; parse[0] = NULLCHAR;
2444                             savingComment = TRUE;
2445                             suppressKibitz = gameMode != IcsObserving ? 2 :
2446                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2447                         } 
2448                         continue;
2449                 } else
2450                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2451                     started = STARTED_CHATTER;
2452                     suppressKibitz = TRUE;
2453                 }
2454             } // [HGM] kibitz: end of patch
2455
2456 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2457
2458             // [HGM] chat: intercept tells by users for which we have an open chat window
2459             channel = -1;
2460             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2461                                            looking_at(buf, &i, "* whispers:") ||
2462                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2463                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2464                 int p;
2465                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2466                 chattingPartner = -1;
2467
2468                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2469                 for(p=0; p<MAX_CHAT; p++) {
2470                     if(channel == atoi(chatPartner[p])) {
2471                     talker[0] = '['; strcat(talker, "]");
2472                     chattingPartner = p; break;
2473                     }
2474                 } else
2475                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2476                 for(p=0; p<MAX_CHAT; p++) {
2477                     if(!strcmp("WHISPER", chatPartner[p])) {
2478                         talker[0] = '['; strcat(talker, "]");
2479                         chattingPartner = p; break;
2480                     }
2481                 }
2482                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2483                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2484                     talker[0] = 0;
2485                     chattingPartner = p; break;
2486                 }
2487                 if(chattingPartner<0) i = oldi; else {
2488                     started = STARTED_COMMENT;
2489                     parse_pos = 0; parse[0] = NULLCHAR;
2490                     savingComment = TRUE;
2491                     suppressKibitz = TRUE;
2492                 }
2493             } // [HGM] chat: end of patch
2494
2495             if (appData.zippyTalk || appData.zippyPlay) {
2496                 /* [DM] Backup address for color zippy lines */
2497                 backup = i;
2498 #if ZIPPY
2499        #ifdef WIN32
2500                if (loggedOn == TRUE)
2501                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2502                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2503        #else
2504                 if (ZippyControl(buf, &i) ||
2505                     ZippyConverse(buf, &i) ||
2506                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2507                       loggedOn = TRUE;
2508                       if (!appData.colorize) continue;
2509                 }
2510        #endif
2511 #endif
2512             } // [DM] 'else { ' deleted
2513                 if (
2514                     /* Regular tells and says */
2515                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2516                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2517                     looking_at(buf, &i, "* says: ") ||
2518                     /* Don't color "message" or "messages" output */
2519                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2520                     looking_at(buf, &i, "*. * at *:*: ") ||
2521                     looking_at(buf, &i, "--* (*:*): ") ||
2522                     /* Message notifications (same color as tells) */
2523                     looking_at(buf, &i, "* has left a message ") ||
2524                     looking_at(buf, &i, "* just sent you a message:\n") ||
2525                     /* Whispers and kibitzes */
2526                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2527                     looking_at(buf, &i, "* kibitzes: ") ||
2528                     /* Channel tells */
2529                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2530
2531                   if (tkind == 1 && strchr(star_match[0], ':')) {
2532                       /* Avoid "tells you:" spoofs in channels */
2533                      tkind = 3;
2534                   }
2535                   if (star_match[0][0] == NULLCHAR ||
2536                       strchr(star_match[0], ' ') ||
2537                       (tkind == 3 && strchr(star_match[1], ' '))) {
2538                     /* Reject bogus matches */
2539                     i = oldi;
2540                   } else {
2541                     if (appData.colorize) {
2542                       if (oldi > next_out) {
2543                         SendToPlayer(&buf[next_out], oldi - next_out);
2544                         next_out = oldi;
2545                       }
2546                       switch (tkind) {
2547                       case 1:
2548                         Colorize(ColorTell, FALSE);
2549                         curColor = ColorTell;
2550                         break;
2551                       case 2:
2552                         Colorize(ColorKibitz, FALSE);
2553                         curColor = ColorKibitz;
2554                         break;
2555                       case 3:
2556                         p = strrchr(star_match[1], '(');
2557                         if (p == NULL) {
2558                           p = star_match[1];
2559                         } else {
2560                           p++;
2561                         }
2562                         if (atoi(p) == 1) {
2563                           Colorize(ColorChannel1, FALSE);
2564                           curColor = ColorChannel1;
2565                         } else {
2566                           Colorize(ColorChannel, FALSE);
2567                           curColor = ColorChannel;
2568                         }
2569                         break;
2570                       case 5:
2571                         curColor = ColorNormal;
2572                         break;
2573                       }
2574                     }
2575                     if (started == STARTED_NONE && appData.autoComment &&
2576                         (gameMode == IcsObserving ||
2577                          gameMode == IcsPlayingWhite ||
2578                          gameMode == IcsPlayingBlack)) {
2579                       parse_pos = i - oldi;
2580                       memcpy(parse, &buf[oldi], parse_pos);
2581                       parse[parse_pos] = NULLCHAR;
2582                       started = STARTED_COMMENT;
2583                       savingComment = TRUE;
2584                     } else {
2585                       started = STARTED_CHATTER;
2586                       savingComment = FALSE;
2587                     }
2588                     loggedOn = TRUE;
2589                     continue;
2590                   }
2591                 }
2592
2593                 if (looking_at(buf, &i, "* s-shouts: ") ||
2594                     looking_at(buf, &i, "* c-shouts: ")) {
2595                     if (appData.colorize) {
2596                         if (oldi > next_out) {
2597                             SendToPlayer(&buf[next_out], oldi - next_out);
2598                             next_out = oldi;
2599                         }
2600                         Colorize(ColorSShout, FALSE);
2601                         curColor = ColorSShout;
2602                     }
2603                     loggedOn = TRUE;
2604                     started = STARTED_CHATTER;
2605                     continue;
2606                 }
2607
2608                 if (looking_at(buf, &i, "--->")) {
2609                     loggedOn = TRUE;
2610                     continue;
2611                 }
2612
2613                 if (looking_at(buf, &i, "* shouts: ") ||
2614                     looking_at(buf, &i, "--> ")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorShout, FALSE);
2621                         curColor = ColorShout;
2622                     }
2623                     loggedOn = TRUE;
2624                     started = STARTED_CHATTER;
2625                     continue;
2626                 }
2627
2628                 if (looking_at( buf, &i, "Challenge:")) {
2629                     if (appData.colorize) {
2630                         if (oldi > next_out) {
2631                             SendToPlayer(&buf[next_out], oldi - next_out);
2632                             next_out = oldi;
2633                         }
2634                         Colorize(ColorChallenge, FALSE);
2635                         curColor = ColorChallenge;
2636                     }
2637                     loggedOn = TRUE;
2638                     continue;
2639                 }
2640
2641                 if (looking_at(buf, &i, "* offers you") ||
2642                     looking_at(buf, &i, "* offers to be") ||
2643                     looking_at(buf, &i, "* would like to") ||
2644                     looking_at(buf, &i, "* requests to") ||
2645                     looking_at(buf, &i, "Your opponent offers") ||
2646                     looking_at(buf, &i, "Your opponent requests")) {
2647
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorRequest, FALSE);
2654                         curColor = ColorRequest;
2655                     }
2656                     continue;
2657                 }
2658
2659                 if (looking_at(buf, &i, "* (*) seeking")) {
2660                     if (appData.colorize) {
2661                         if (oldi > next_out) {
2662                             SendToPlayer(&buf[next_out], oldi - next_out);
2663                             next_out = oldi;
2664                         }
2665                         Colorize(ColorSeek, FALSE);
2666                         curColor = ColorSeek;
2667                     }
2668                     continue;
2669             }
2670
2671             if (looking_at(buf, &i, "\\   ")) {
2672                 if (prevColor != ColorNormal) {
2673                     if (oldi > next_out) {
2674                         SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = oldi;
2676                     }
2677                     Colorize(prevColor, TRUE);
2678                     curColor = prevColor;
2679                 }
2680                 if (savingComment) {
2681                     parse_pos = i - oldi;
2682                     memcpy(parse, &buf[oldi], parse_pos);
2683                     parse[parse_pos] = NULLCHAR;
2684                     started = STARTED_COMMENT;
2685                 } else {
2686                     started = STARTED_CHATTER;
2687                 }
2688                 continue;
2689             }
2690
2691             if (looking_at(buf, &i, "Black Strength :") ||
2692                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2693                 looking_at(buf, &i, "<10>") ||
2694                 looking_at(buf, &i, "#@#")) {
2695                 /* Wrong board style */
2696                 loggedOn = TRUE;
2697                 SendToICS(ics_prefix);
2698                 SendToICS("set style 12\n");
2699                 SendToICS(ics_prefix);
2700                 SendToICS("refresh\n");
2701                 continue;
2702             }
2703             
2704             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2705                 ICSInitScript();
2706                 have_sent_ICS_logon = 1;
2707                 continue;
2708             }
2709               
2710             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2711                 (looking_at(buf, &i, "\n<12> ") ||
2712                  looking_at(buf, &i, "<12> "))) {
2713                 loggedOn = TRUE;
2714                 if (oldi > next_out) {
2715                     SendToPlayer(&buf[next_out], oldi - next_out);
2716                 }
2717                 next_out = i;
2718                 started = STARTED_BOARD;
2719                 parse_pos = 0;
2720                 continue;
2721             }
2722
2723             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2724                 looking_at(buf, &i, "<b1> ")) {
2725                 if (oldi > next_out) {
2726                     SendToPlayer(&buf[next_out], oldi - next_out);
2727                 }
2728                 next_out = i;
2729                 started = STARTED_HOLDINGS;
2730                 parse_pos = 0;
2731                 continue;
2732             }
2733
2734             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2735                 loggedOn = TRUE;
2736                 /* Header for a move list -- first line */
2737
2738                 switch (ics_getting_history) {
2739                   case H_FALSE:
2740                     switch (gameMode) {
2741                       case IcsIdle:
2742                       case BeginningOfGame:
2743                         /* User typed "moves" or "oldmoves" while we
2744                            were idle.  Pretend we asked for these
2745                            moves and soak them up so user can step
2746                            through them and/or save them.
2747                            */
2748                         Reset(FALSE, TRUE);
2749                         gameMode = IcsObserving;
2750                         ModeHighlight();
2751                         ics_gamenum = -1;
2752                         ics_getting_history = H_GOT_UNREQ_HEADER;
2753                         break;
2754                       case EditGame: /*?*/
2755                       case EditPosition: /*?*/
2756                         /* Should above feature work in these modes too? */
2757                         /* For now it doesn't */
2758                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2759                         break;
2760                       default:
2761                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2762                         break;
2763                     }
2764                     break;
2765                   case H_REQUESTED:
2766                     /* Is this the right one? */
2767                     if (gameInfo.white && gameInfo.black &&
2768                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2769                         strcmp(gameInfo.black, star_match[2]) == 0) {
2770                         /* All is well */
2771                         ics_getting_history = H_GOT_REQ_HEADER;
2772                     }
2773                     break;
2774                   case H_GOT_REQ_HEADER:
2775                   case H_GOT_UNREQ_HEADER:
2776                   case H_GOT_UNWANTED_HEADER:
2777                   case H_GETTING_MOVES:
2778                     /* Should not happen */
2779                     DisplayError(_("Error gathering move list: two headers"), 0);
2780                     ics_getting_history = H_FALSE;
2781                     break;
2782                 }
2783
2784                 /* Save player ratings into gameInfo if needed */
2785                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2786                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2787                     (gameInfo.whiteRating == -1 ||
2788                      gameInfo.blackRating == -1)) {
2789
2790                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2791                     gameInfo.blackRating = string_to_rating(star_match[3]);
2792                     if (appData.debugMode)
2793                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2794                               gameInfo.whiteRating, gameInfo.blackRating);
2795                 }
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i,
2800               "* * match, initial time: * minute*, increment: * second")) {
2801                 /* Header for a move list -- second line */
2802                 /* Initial board will follow if this is a wild game */
2803                 if (gameInfo.event != NULL) free(gameInfo.event);
2804                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2805                 gameInfo.event = StrSave(str);
2806                 /* [HGM] we switched variant. Translate boards if needed. */
2807                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2808                 continue;
2809             }
2810
2811             if (looking_at(buf, &i, "Move  ")) {
2812                 /* Beginning of a move list */
2813                 switch (ics_getting_history) {
2814                   case H_FALSE:
2815                     /* Normally should not happen */
2816                     /* Maybe user hit reset while we were parsing */
2817                     break;
2818                   case H_REQUESTED:
2819                     /* Happens if we are ignoring a move list that is not
2820                      * the one we just requested.  Common if the user
2821                      * tries to observe two games without turning off
2822                      * getMoveList */
2823                     break;
2824                   case H_GETTING_MOVES:
2825                     /* Should not happen */
2826                     DisplayError(_("Error gathering move list: nested"), 0);
2827                     ics_getting_history = H_FALSE;
2828                     break;
2829                   case H_GOT_REQ_HEADER:
2830                     ics_getting_history = H_GETTING_MOVES;
2831                     started = STARTED_MOVES;
2832                     parse_pos = 0;
2833                     if (oldi > next_out) {
2834                         SendToPlayer(&buf[next_out], oldi - next_out);
2835                     }
2836                     break;
2837                   case H_GOT_UNREQ_HEADER:
2838                     ics_getting_history = H_GETTING_MOVES;
2839                     started = STARTED_MOVES_NOHIDE;
2840                     parse_pos = 0;
2841                     break;
2842                   case H_GOT_UNWANTED_HEADER:
2843                     ics_getting_history = H_FALSE;
2844                     break;
2845                 }
2846                 continue;
2847             }                           
2848             
2849             if (looking_at(buf, &i, "% ") ||
2850                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2851                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2852                 savingComment = FALSE;
2853                 switch (started) {
2854                   case STARTED_MOVES:
2855                   case STARTED_MOVES_NOHIDE:
2856                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2857                     parse[parse_pos + i - oldi] = NULLCHAR;
2858                     ParseGameHistory(parse);
2859 #if ZIPPY
2860                     if (appData.zippyPlay && first.initDone) {
2861                         FeedMovesToProgram(&first, forwardMostMove);
2862                         if (gameMode == IcsPlayingWhite) {
2863                             if (WhiteOnMove(forwardMostMove)) {
2864                                 if (first.sendTime) {
2865                                   if (first.useColors) {
2866                                     SendToProgram("black\n", &first); 
2867                                   }
2868                                   SendTimeRemaining(&first, TRUE);
2869                                 }
2870                                 if (first.useColors) {
2871                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2872                                 }
2873                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2874                                 first.maybeThinking = TRUE;
2875                             } else {
2876                                 if (first.usePlayother) {
2877                                   if (first.sendTime) {
2878                                     SendTimeRemaining(&first, TRUE);
2879                                   }
2880                                   SendToProgram("playother\n", &first);
2881                                   firstMove = FALSE;
2882                                 } else {
2883                                   firstMove = TRUE;
2884                                 }
2885                             }
2886                         } else if (gameMode == IcsPlayingBlack) {
2887                             if (!WhiteOnMove(forwardMostMove)) {
2888                                 if (first.sendTime) {
2889                                   if (first.useColors) {
2890                                     SendToProgram("white\n", &first);
2891                                   }
2892                                   SendTimeRemaining(&first, FALSE);
2893                                 }
2894                                 if (first.useColors) {
2895                                   SendToProgram("black\n", &first);
2896                                 }
2897                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2898                                 first.maybeThinking = TRUE;
2899                             } else {
2900                                 if (first.usePlayother) {
2901                                   if (first.sendTime) {
2902                                     SendTimeRemaining(&first, FALSE);
2903                                   }
2904                                   SendToProgram("playother\n", &first);
2905                                   firstMove = FALSE;
2906                                 } else {
2907                                   firstMove = TRUE;
2908                                 }
2909                             }
2910                         }                       
2911                     }
2912 #endif
2913                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2914                         /* Moves came from oldmoves or moves command
2915                            while we weren't doing anything else.
2916                            */
2917                         currentMove = forwardMostMove;
2918                         ClearHighlights();/*!!could figure this out*/
2919                         flipView = appData.flipView;
2920                         DrawPosition(TRUE, boards[currentMove]);
2921                         DisplayBothClocks();
2922                         sprintf(str, "%s vs. %s",
2923                                 gameInfo.white, gameInfo.black);
2924                         DisplayTitle(str);
2925                         gameMode = IcsIdle;
2926                     } else {
2927                         /* Moves were history of an active game */
2928                         if (gameInfo.resultDetails != NULL) {
2929                             free(gameInfo.resultDetails);
2930                             gameInfo.resultDetails = NULL;
2931                         }
2932                     }
2933                     HistorySet(parseList, backwardMostMove,
2934                                forwardMostMove, currentMove-1);
2935                     DisplayMove(currentMove - 1);
2936                     if (started == STARTED_MOVES) next_out = i;
2937                     started = STARTED_NONE;
2938                     ics_getting_history = H_FALSE;
2939                     break;
2940
2941                   case STARTED_OBSERVE:
2942                     started = STARTED_NONE;
2943                     SendToICS(ics_prefix);
2944                     SendToICS("refresh\n");
2945                     break;
2946
2947                   default:
2948                     break;
2949                 }
2950                 if(bookHit) { // [HGM] book: simulate book reply
2951                     static char bookMove[MSG_SIZ]; // a bit generous?
2952
2953                     programStats.nodes = programStats.depth = programStats.time = 
2954                     programStats.score = programStats.got_only_move = 0;
2955                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2956
2957                     strcpy(bookMove, "move ");
2958                     strcat(bookMove, bookHit);
2959                     HandleMachineMove(bookMove, &first);
2960                 }
2961                 continue;
2962             }
2963             
2964             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2965                  started == STARTED_HOLDINGS ||
2966                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2967                 /* Accumulate characters in move list or board */
2968                 parse[parse_pos++] = buf[i];
2969             }
2970             
2971             /* Start of game messages.  Mostly we detect start of game
2972                when the first board image arrives.  On some versions
2973                of the ICS, though, we need to do a "refresh" after starting
2974                to observe in order to get the current board right away. */
2975             if (looking_at(buf, &i, "Adding game * to observation list")) {
2976                 started = STARTED_OBSERVE;
2977                 continue;
2978             }
2979
2980             /* Handle auto-observe */
2981             if (appData.autoObserve &&
2982                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2983                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2984                 char *player;
2985                 /* Choose the player that was highlighted, if any. */
2986                 if (star_match[0][0] == '\033' ||
2987                     star_match[1][0] != '\033') {
2988                     player = star_match[0];
2989                 } else {
2990                     player = star_match[2];
2991                 }
2992                 sprintf(str, "%sobserve %s\n",
2993                         ics_prefix, StripHighlightAndTitle(player));
2994                 SendToICS(str);
2995
2996                 /* Save ratings from notify string */
2997                 strcpy(player1Name, star_match[0]);
2998                 player1Rating = string_to_rating(star_match[1]);
2999                 strcpy(player2Name, star_match[2]);
3000                 player2Rating = string_to_rating(star_match[3]);
3001
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, 
3004                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3005                           player1Name, player1Rating,
3006                           player2Name, player2Rating);
3007
3008                 continue;
3009             }
3010
3011             /* Deal with automatic examine mode after a game,
3012                and with IcsObserving -> IcsExamining transition */
3013             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3014                 looking_at(buf, &i, "has made you an examiner of game *")) {
3015
3016                 int gamenum = atoi(star_match[0]);
3017                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3018                     gamenum == ics_gamenum) {
3019                     /* We were already playing or observing this game;
3020                        no need to refetch history */
3021                     gameMode = IcsExamining;
3022                     if (pausing) {
3023                         pauseExamForwardMostMove = forwardMostMove;
3024                     } else if (currentMove < forwardMostMove) {
3025                         ForwardInner(forwardMostMove);
3026                     }
3027                 } else {
3028                     /* I don't think this case really can happen */
3029                     SendToICS(ics_prefix);
3030                     SendToICS("refresh\n");
3031                 }
3032                 continue;
3033             }    
3034             
3035             /* Error messages */
3036 //          if (ics_user_moved) {
3037             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3038                 if (looking_at(buf, &i, "Illegal move") ||
3039                     looking_at(buf, &i, "Not a legal move") ||
3040                     looking_at(buf, &i, "Your king is in check") ||
3041                     looking_at(buf, &i, "It isn't your turn") ||
3042                     looking_at(buf, &i, "It is not your move")) {
3043                     /* Illegal move */
3044                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3045                         currentMove = --forwardMostMove;
3046                         DisplayMove(currentMove - 1); /* before DMError */
3047                         DrawPosition(FALSE, boards[currentMove]);
3048                         SwitchClocks();
3049                         DisplayBothClocks();
3050                     }
3051                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3052                     ics_user_moved = 0;
3053                     continue;
3054                 }
3055             }
3056
3057             if (looking_at(buf, &i, "still have time") ||
3058                 looking_at(buf, &i, "not out of time") ||
3059                 looking_at(buf, &i, "either player is out of time") ||
3060                 looking_at(buf, &i, "has timeseal; checking")) {
3061                 /* We must have called his flag a little too soon */
3062                 whiteFlag = blackFlag = FALSE;
3063                 continue;
3064             }
3065
3066             if (looking_at(buf, &i, "added * seconds to") ||
3067                 looking_at(buf, &i, "seconds were added to")) {
3068                 /* Update the clocks */
3069                 SendToICS(ics_prefix);
3070                 SendToICS("refresh\n");
3071                 continue;
3072             }
3073
3074             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3075                 ics_clock_paused = TRUE;
3076                 StopClocks();
3077                 continue;
3078             }
3079
3080             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3081                 ics_clock_paused = FALSE;
3082                 StartClocks();
3083                 continue;
3084             }
3085
3086             /* Grab player ratings from the Creating: message.
3087                Note we have to check for the special case when
3088                the ICS inserts things like [white] or [black]. */
3089             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3090                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3091                 /* star_matches:
3092                    0    player 1 name (not necessarily white)
3093                    1    player 1 rating
3094                    2    empty, white, or black (IGNORED)
3095                    3    player 2 name (not necessarily black)
3096                    4    player 2 rating
3097                    
3098                    The names/ratings are sorted out when the game
3099                    actually starts (below).
3100                 */
3101                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3102                 player1Rating = string_to_rating(star_match[1]);
3103                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3104                 player2Rating = string_to_rating(star_match[4]);
3105
3106                 if (appData.debugMode)
3107                   fprintf(debugFP, 
3108                           "Ratings from 'Creating:' %s %d, %s %d\n",
3109                           player1Name, player1Rating,
3110                           player2Name, player2Rating);
3111
3112                 continue;
3113             }
3114             
3115             /* Improved generic start/end-of-game messages */
3116             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3117                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3118                 /* If tkind == 0: */
3119                 /* star_match[0] is the game number */
3120                 /*           [1] is the white player's name */
3121                 /*           [2] is the black player's name */
3122                 /* For end-of-game: */
3123                 /*           [3] is the reason for the game end */
3124                 /*           [4] is a PGN end game-token, preceded by " " */
3125                 /* For start-of-game: */
3126                 /*           [3] begins with "Creating" or "Continuing" */
3127                 /*           [4] is " *" or empty (don't care). */
3128                 int gamenum = atoi(star_match[0]);
3129                 char *whitename, *blackname, *why, *endtoken;
3130                 ChessMove endtype = (ChessMove) 0;
3131
3132                 if (tkind == 0) {
3133                   whitename = star_match[1];
3134                   blackname = star_match[2];
3135                   why = star_match[3];
3136                   endtoken = star_match[4];
3137                 } else {
3138                   whitename = star_match[1];
3139                   blackname = star_match[3];
3140                   why = star_match[5];
3141                   endtoken = star_match[6];
3142                 }
3143
3144                 /* Game start messages */
3145                 if (strncmp(why, "Creating ", 9) == 0 ||
3146                     strncmp(why, "Continuing ", 11) == 0) {
3147                     gs_gamenum = gamenum;
3148                     strcpy(gs_kind, strchr(why, ' ') + 1);
3149 #if ZIPPY
3150                     if (appData.zippyPlay) {
3151                         ZippyGameStart(whitename, blackname);
3152                     }
3153 #endif /*ZIPPY*/
3154                     continue;
3155                 }
3156
3157                 /* Game end messages */
3158                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3159                     ics_gamenum != gamenum) {
3160                     continue;
3161                 }
3162                 while (endtoken[0] == ' ') endtoken++;
3163                 switch (endtoken[0]) {
3164                   case '*':
3165                   default:
3166                     endtype = GameUnfinished;
3167                     break;
3168                   case '0':
3169                     endtype = BlackWins;
3170                     break;
3171                   case '1':
3172                     if (endtoken[1] == '/')
3173                       endtype = GameIsDrawn;
3174                     else
3175                       endtype = WhiteWins;
3176                     break;
3177                 }
3178                 GameEnds(endtype, why, GE_ICS);
3179 #if ZIPPY
3180                 if (appData.zippyPlay && first.initDone) {
3181                     ZippyGameEnd(endtype, why);
3182                     if (first.pr == NULL) {
3183                       /* Start the next process early so that we'll
3184                          be ready for the next challenge */
3185                       StartChessProgram(&first);
3186                     }
3187                     /* Send "new" early, in case this command takes
3188                        a long time to finish, so that we'll be ready
3189                        for the next challenge. */
3190                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3191                     Reset(TRUE, TRUE);
3192                 }
3193 #endif /*ZIPPY*/
3194                 continue;
3195             }
3196
3197             if (looking_at(buf, &i, "Removing game * from observation") ||
3198                 looking_at(buf, &i, "no longer observing game *") ||
3199                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3200                 if (gameMode == IcsObserving &&
3201                     atoi(star_match[0]) == ics_gamenum)
3202                   {
3203                       /* icsEngineAnalyze */
3204                       if (appData.icsEngineAnalyze) {
3205                             ExitAnalyzeMode();
3206                             ModeHighlight();
3207                       }
3208                       StopClocks();
3209                       gameMode = IcsIdle;
3210                       ics_gamenum = -1;
3211                       ics_user_moved = FALSE;
3212                   }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i, "no longer examining game *")) {
3217                 if (gameMode == IcsExamining &&
3218                     atoi(star_match[0]) == ics_gamenum)
3219                   {
3220                       gameMode = IcsIdle;
3221                       ics_gamenum = -1;
3222                       ics_user_moved = FALSE;
3223                   }
3224                 continue;
3225             }
3226
3227             /* Advance leftover_start past any newlines we find,
3228                so only partial lines can get reparsed */
3229             if (looking_at(buf, &i, "\n")) {
3230                 prevColor = curColor;
3231                 if (curColor != ColorNormal) {
3232                     if (oldi > next_out) {
3233                         SendToPlayer(&buf[next_out], oldi - next_out);
3234                         next_out = oldi;
3235                     }
3236                     Colorize(ColorNormal, FALSE);
3237                     curColor = ColorNormal;
3238                 }
3239                 if (started == STARTED_BOARD) {
3240                     started = STARTED_NONE;
3241                     parse[parse_pos] = NULLCHAR;
3242                     ParseBoard12(parse);
3243                     ics_user_moved = 0;
3244
3245                     /* Send premove here */
3246                     if (appData.premove) {
3247                       char str[MSG_SIZ];
3248                       if (currentMove == 0 &&
3249                           gameMode == IcsPlayingWhite &&
3250                           appData.premoveWhite) {
3251                         sprintf(str, "%s\n", appData.premoveWhiteText);
3252                         if (appData.debugMode)
3253                           fprintf(debugFP, "Sending premove:\n");
3254                         SendToICS(str);
3255                       } else if (currentMove == 1 &&
3256                                  gameMode == IcsPlayingBlack &&
3257                                  appData.premoveBlack) {
3258                         sprintf(str, "%s\n", appData.premoveBlackText);
3259                         if (appData.debugMode)
3260                           fprintf(debugFP, "Sending premove:\n");
3261                         SendToICS(str);
3262                       } else if (gotPremove) {
3263                         gotPremove = 0;
3264                         ClearPremoveHighlights();
3265                         if (appData.debugMode)
3266                           fprintf(debugFP, "Sending premove:\n");
3267                           UserMoveEvent(premoveFromX, premoveFromY, 
3268                                         premoveToX, premoveToY, 
3269                                         premovePromoChar);
3270                       }
3271                     }
3272
3273                     /* Usually suppress following prompt */
3274                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3275                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3276                         if (looking_at(buf, &i, "*% ")) {
3277                             savingComment = FALSE;
3278                         }
3279                     }
3280                     next_out = i;
3281                 } else if (started == STARTED_HOLDINGS) {
3282                     int gamenum;
3283                     char new_piece[MSG_SIZ];
3284                     started = STARTED_NONE;
3285                     parse[parse_pos] = NULLCHAR;
3286                     if (appData.debugMode)
3287                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3288                                                         parse, currentMove);
3289                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3290                         gamenum == ics_gamenum) {
3291                         if (gameInfo.variant == VariantNormal) {
3292                           /* [HGM] We seem to switch variant during a game!
3293                            * Presumably no holdings were displayed, so we have
3294                            * to move the position two files to the right to
3295                            * create room for them!
3296                            */
3297                           VariantClass newVariant;
3298                           switch(gameInfo.boardWidth) { // base guess on board width
3299                                 case 9:  newVariant = VariantShogi; break;
3300                                 case 10: newVariant = VariantGreat; break;
3301                                 default: newVariant = VariantCrazyhouse; break;
3302                           }
3303                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3304                           /* Get a move list just to see the header, which
3305                              will tell us whether this is really bug or zh */
3306                           if (ics_getting_history == H_FALSE) {
3307                             ics_getting_history = H_REQUESTED;
3308                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3309                             SendToICS(str);
3310                           }
3311                         }
3312                         new_piece[0] = NULLCHAR;
3313                         sscanf(parse, "game %d white [%s black [%s <- %s",
3314                                &gamenum, white_holding, black_holding,
3315                                new_piece);
3316                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3317                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3318                         /* [HGM] copy holdings to board holdings area */
3319                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3320                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3321                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3322 #if ZIPPY
3323                         if (appData.zippyPlay && first.initDone) {
3324                             ZippyHoldings(white_holding, black_holding,
3325                                           new_piece);
3326                         }
3327 #endif /*ZIPPY*/
3328                         if (tinyLayout || smallLayout) {
3329                             char wh[16], bh[16];
3330                             PackHolding(wh, white_holding);
3331                             PackHolding(bh, black_holding);
3332                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3333                                     gameInfo.white, gameInfo.black);
3334                         } else {
3335                             sprintf(str, "%s [%s] vs. %s [%s]",
3336                                     gameInfo.white, white_holding,
3337                                     gameInfo.black, black_holding);
3338                         }
3339
3340                         DrawPosition(FALSE, boards[currentMove]);
3341                         DisplayTitle(str);
3342                     }
3343                     /* Suppress following prompt */
3344                     if (looking_at(buf, &i, "*% ")) {
3345                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3346                         savingComment = FALSE;
3347                     }
3348                     next_out = i;
3349                 }
3350                 continue;
3351             }
3352
3353             i++;                /* skip unparsed character and loop back */
3354         }
3355         
3356         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3357             started != STARTED_HOLDINGS && i > next_out) {
3358             SendToPlayer(&buf[next_out], i - next_out);
3359             next_out = i;
3360         }
3361         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3362         
3363         leftover_len = buf_len - leftover_start;
3364         /* if buffer ends with something we couldn't parse,
3365            reparse it after appending the next read */
3366         
3367     } else if (count == 0) {
3368         RemoveInputSource(isr);
3369         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3370     } else {
3371         DisplayFatalError(_("Error reading from ICS"), error, 1);
3372     }
3373 }
3374
3375
3376 /* Board style 12 looks like this:
3377    
3378    <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
3379    
3380  * The "<12> " is stripped before it gets to this routine.  The two
3381  * trailing 0's (flip state and clock ticking) are later addition, and
3382  * some chess servers may not have them, or may have only the first.
3383  * Additional trailing fields may be added in the future.  
3384  */
3385
3386 #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"
3387
3388 #define RELATION_OBSERVING_PLAYED    0
3389 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3390 #define RELATION_PLAYING_MYMOVE      1
3391 #define RELATION_PLAYING_NOTMYMOVE  -1
3392 #define RELATION_EXAMINING           2
3393 #define RELATION_ISOLATED_BOARD     -3
3394 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3395
3396 void
3397 ParseBoard12(string)
3398      char *string;
3399
3400     GameMode newGameMode;
3401     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3402     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3403     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3404     char to_play, board_chars[200];
3405     char move_str[500], str[500], elapsed_time[500];
3406     char black[32], white[32];
3407     Board board;
3408     int prevMove = currentMove;
3409     int ticking = 2;
3410     ChessMove moveType;
3411     int fromX, fromY, toX, toY;
3412     char promoChar;
3413     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3414     char *bookHit = NULL; // [HGM] book
3415     Boolean weird = FALSE, reqFlag = FALSE;
3416
3417     fromX = fromY = toX = toY = -1;
3418     
3419     newGame = FALSE;
3420
3421     if (appData.debugMode)
3422       fprintf(debugFP, _("Parsing board: %s\n"), string);
3423
3424     move_str[0] = NULLCHAR;
3425     elapsed_time[0] = NULLCHAR;
3426     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3427         int  i = 0, j;
3428         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3429             if(string[i] == ' ') { ranks++; files = 0; }
3430             else files++;
3431             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3432             i++;
3433         }
3434         for(j = 0; j <i; j++) board_chars[j] = string[j];
3435         board_chars[i] = '\0';
3436         string += i + 1;
3437     }
3438     n = sscanf(string, PATTERN, &to_play, &double_push,
3439                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3440                &gamenum, white, black, &relation, &basetime, &increment,
3441                &white_stren, &black_stren, &white_time, &black_time,
3442                &moveNum, str, elapsed_time, move_str, &ics_flip,
3443                &ticking);
3444
3445     if (n < 21) {
3446         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3447         DisplayError(str, 0);
3448         return;
3449     }
3450
3451     /* Convert the move number to internal form */
3452     moveNum = (moveNum - 1) * 2;
3453     if (to_play == 'B') moveNum++;
3454     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3455       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3456                         0, 1);
3457       return;
3458     }
3459     
3460     switch (relation) {
3461       case RELATION_OBSERVING_PLAYED:
3462       case RELATION_OBSERVING_STATIC:
3463         if (gamenum == -1) {
3464             /* Old ICC buglet */
3465             relation = RELATION_OBSERVING_STATIC;
3466         }
3467         newGameMode = IcsObserving;
3468         break;
3469       case RELATION_PLAYING_MYMOVE:
3470       case RELATION_PLAYING_NOTMYMOVE:
3471         newGameMode =
3472           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3473             IcsPlayingWhite : IcsPlayingBlack;
3474         break;
3475       case RELATION_EXAMINING:
3476         newGameMode = IcsExamining;
3477         break;
3478       case RELATION_ISOLATED_BOARD:
3479       default:
3480         /* Just display this board.  If user was doing something else,
3481            we will forget about it until the next board comes. */ 
3482         newGameMode = IcsIdle;
3483         break;
3484       case RELATION_STARTING_POSITION:
3485         newGameMode = gameMode;
3486         break;
3487     }
3488     
3489     /* Modify behavior for initial board display on move listing
3490        of wild games.
3491        */
3492     switch (ics_getting_history) {
3493       case H_FALSE:
3494       case H_REQUESTED:
3495         break;
3496       case H_GOT_REQ_HEADER:
3497       case H_GOT_UNREQ_HEADER:
3498         /* This is the initial position of the current game */
3499         gamenum = ics_gamenum;
3500         moveNum = 0;            /* old ICS bug workaround */
3501         if (to_play == 'B') {
3502           startedFromSetupPosition = TRUE;
3503           blackPlaysFirst = TRUE;
3504           moveNum = 1;
3505           if (forwardMostMove == 0) forwardMostMove = 1;
3506           if (backwardMostMove == 0) backwardMostMove = 1;
3507           if (currentMove == 0) currentMove = 1;
3508         }
3509         newGameMode = gameMode;
3510         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3511         break;
3512       case H_GOT_UNWANTED_HEADER:
3513         /* This is an initial board that we don't want */
3514         return;
3515       case H_GETTING_MOVES:
3516         /* Should not happen */
3517         DisplayError(_("Error gathering move list: extra board"), 0);
3518         ics_getting_history = H_FALSE;
3519         return;
3520     }
3521
3522    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3523                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3524      /* [HGM] We seem to have switched variant unexpectedly
3525       * Try to guess new variant from board size
3526       */
3527           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3528           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3529           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3530           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3531           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3532           if(!weird) newVariant = VariantNormal;
3533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3534           /* Get a move list just to see the header, which
3535              will tell us whether this is really bug or zh */
3536           if (ics_getting_history == H_FALSE) {
3537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3538             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3539             SendToICS(str);
3540           }
3541     }
3542     
3543     /* Take action if this is the first board of a new game, or of a
3544        different game than is currently being displayed.  */
3545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3546         relation == RELATION_ISOLATED_BOARD) {
3547         
3548         /* Forget the old game and get the history (if any) of the new one */
3549         if (gameMode != BeginningOfGame) {
3550           Reset(TRUE, TRUE);
3551         }
3552         newGame = TRUE;
3553         if (appData.autoRaiseBoard) BoardToTop();
3554         prevMove = -3;
3555         if (gamenum == -1) {
3556             newGameMode = IcsIdle;
3557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3558                    appData.getMoveList && !reqFlag) {
3559             /* Need to get game history */
3560             ics_getting_history = H_REQUESTED;
3561             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3562             SendToICS(str);
3563         }
3564         
3565         /* Initially flip the board to have black on the bottom if playing
3566            black or if the ICS flip flag is set, but let the user change
3567            it with the Flip View button. */
3568         flipView = appData.autoFlipView ? 
3569           (newGameMode == IcsPlayingBlack) || ics_flip :
3570           appData.flipView;
3571         
3572         /* Done with values from previous mode; copy in new ones */
3573         gameMode = newGameMode;
3574         ModeHighlight();
3575         ics_gamenum = gamenum;
3576         if (gamenum == gs_gamenum) {
3577             int klen = strlen(gs_kind);
3578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3579             sprintf(str, "ICS %s", gs_kind);
3580             gameInfo.event = StrSave(str);
3581         } else {
3582             gameInfo.event = StrSave("ICS game");
3583         }
3584         gameInfo.site = StrSave(appData.icsHost);
3585         gameInfo.date = PGNDate();
3586         gameInfo.round = StrSave("-");
3587         gameInfo.white = StrSave(white);
3588         gameInfo.black = StrSave(black);
3589         timeControl = basetime * 60 * 1000;
3590         timeControl_2 = 0;
3591         timeIncrement = increment * 1000;
3592         movesPerSession = 0;
3593         gameInfo.timeControl = TimeControlTagValue();
3594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3595   if (appData.debugMode) {
3596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3598     setbuf(debugFP, NULL);
3599   }
3600
3601         gameInfo.outOfBook = NULL;
3602         
3603         /* Do we have the ratings? */
3604         if (strcmp(player1Name, white) == 0 &&
3605             strcmp(player2Name, black) == 0) {
3606             if (appData.debugMode)
3607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3608                       player1Rating, player2Rating);
3609             gameInfo.whiteRating = player1Rating;
3610             gameInfo.blackRating = player2Rating;
3611         } else if (strcmp(player2Name, white) == 0 &&
3612                    strcmp(player1Name, black) == 0) {
3613             if (appData.debugMode)
3614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3615                       player2Rating, player1Rating);
3616             gameInfo.whiteRating = player2Rating;
3617             gameInfo.blackRating = player1Rating;
3618         }
3619         player1Name[0] = player2Name[0] = NULLCHAR;
3620
3621         /* Silence shouts if requested */
3622         if (appData.quietPlay &&
3623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3624             SendToICS(ics_prefix);
3625             SendToICS("set shout 0\n");
3626         }
3627     }
3628     
3629     /* Deal with midgame name changes */
3630     if (!newGame) {
3631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3632             if (gameInfo.white) free(gameInfo.white);
3633             gameInfo.white = StrSave(white);
3634         }
3635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3636             if (gameInfo.black) free(gameInfo.black);
3637             gameInfo.black = StrSave(black);
3638         }
3639     }
3640     
3641     /* Throw away game result if anything actually changes in examine mode */
3642     if (gameMode == IcsExamining && !newGame) {
3643         gameInfo.result = GameUnfinished;
3644         if (gameInfo.resultDetails != NULL) {
3645             free(gameInfo.resultDetails);
3646             gameInfo.resultDetails = NULL;
3647         }
3648     }
3649     
3650     /* In pausing && IcsExamining mode, we ignore boards coming
3651        in if they are in a different variation than we are. */
3652     if (pauseExamInvalid) return;
3653     if (pausing && gameMode == IcsExamining) {
3654         if (moveNum <= pauseExamForwardMostMove) {
3655             pauseExamInvalid = TRUE;
3656             forwardMostMove = pauseExamForwardMostMove;
3657             return;
3658         }
3659     }
3660     
3661   if (appData.debugMode) {
3662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3663   }
3664     /* Parse the board */
3665     for (k = 0; k < ranks; k++) {
3666       for (j = 0; j < files; j++)
3667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3668       if(gameInfo.holdingsWidth > 1) {
3669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3671       }
3672     }
3673     CopyBoard(boards[moveNum], board);
3674     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3675     if (moveNum == 0) {
3676         startedFromSetupPosition =
3677           !CompareBoards(board, initialPosition);
3678         if(startedFromSetupPosition)
3679             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3680     }
3681
3682     /* [HGM] Set castling rights. Take the outermost Rooks,
3683        to make it also work for FRC opening positions. Note that board12
3684        is really defective for later FRC positions, as it has no way to
3685        indicate which Rook can castle if they are on the same side of King.
3686        For the initial position we grant rights to the outermost Rooks,
3687        and remember thos rights, and we then copy them on positions
3688        later in an FRC game. This means WB might not recognize castlings with
3689        Rooks that have moved back to their original position as illegal,
3690        but in ICS mode that is not its job anyway.
3691     */
3692     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3693     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3694
3695         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3696             if(board[0][i] == WhiteRook) j = i;
3697         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3698         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3699             if(board[0][i] == WhiteRook) j = i;
3700         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3702             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707
3708         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3709         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3710             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712             if(board[BOARD_HEIGHT-1][k] == bKing)
3713                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3714     } else { int r;
3715         r = boards[moveNum][CASTLING][0] = initialRights[0];
3716         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3717         r = boards[moveNum][CASTLING][1] = initialRights[1];
3718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3719         r = boards[moveNum][CASTLING][3] = initialRights[3];
3720         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3721         r = boards[moveNum][CASTLING][4] = initialRights[4];
3722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3723         /* wildcastle kludge: always assume King has rights */
3724         r = boards[moveNum][CASTLING][2] = initialRights[2];
3725         r = boards[moveNum][CASTLING][5] = initialRights[5];
3726     }
3727     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3728     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3729
3730     
3731     if (ics_getting_history == H_GOT_REQ_HEADER ||
3732         ics_getting_history == H_GOT_UNREQ_HEADER) {
3733         /* This was an initial position from a move list, not
3734            the current position */
3735         return;
3736     }
3737     
3738     /* Update currentMove and known move number limits */
3739     newMove = newGame || moveNum > forwardMostMove;
3740
3741     if (newGame) {
3742         forwardMostMove = backwardMostMove = currentMove = moveNum;
3743         if (gameMode == IcsExamining && moveNum == 0) {
3744           /* Workaround for ICS limitation: we are not told the wild
3745              type when starting to examine a game.  But if we ask for
3746              the move list, the move list header will tell us */
3747             ics_getting_history = H_REQUESTED;
3748             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3749             SendToICS(str);
3750         }
3751     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3752                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3753 #if ZIPPY
3754         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3755         /* [HGM] applied this also to an engine that is silently watching        */
3756         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3757             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3758             gameInfo.variant == currentlyInitializedVariant) {
3759           takeback = forwardMostMove - moveNum;
3760           for (i = 0; i < takeback; i++) {
3761             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3762             SendToProgram("undo\n", &first);
3763           }
3764         }
3765 #endif
3766
3767         forwardMostMove = moveNum;
3768         if (!pausing || currentMove > forwardMostMove)
3769           currentMove = forwardMostMove;
3770     } else {
3771         /* New part of history that is not contiguous with old part */ 
3772         if (pausing && gameMode == IcsExamining) {
3773             pauseExamInvalid = TRUE;
3774             forwardMostMove = pauseExamForwardMostMove;
3775             return;
3776         }
3777         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3778 #if ZIPPY
3779             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3780                 // [HGM] when we will receive the move list we now request, it will be
3781                 // fed to the engine from the first move on. So if the engine is not
3782                 // in the initial position now, bring it there.
3783                 InitChessProgram(&first, 0);
3784             }
3785 #endif
3786             ics_getting_history = H_REQUESTED;
3787             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3788             SendToICS(str);
3789         }
3790         forwardMostMove = backwardMostMove = currentMove = moveNum;
3791     }
3792     
3793     /* Update the clocks */
3794     if (strchr(elapsed_time, '.')) {
3795       /* Time is in ms */
3796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3798     } else {
3799       /* Time is in seconds */
3800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3802     }
3803       
3804
3805 #if ZIPPY
3806     if (appData.zippyPlay && newGame &&
3807         gameMode != IcsObserving && gameMode != IcsIdle &&
3808         gameMode != IcsExamining)
3809       ZippyFirstBoard(moveNum, basetime, increment);
3810 #endif
3811     
3812     /* Put the move on the move list, first converting
3813        to canonical algebraic form. */
3814     if (moveNum > 0) {
3815   if (appData.debugMode) {
3816     if (appData.debugMode) { int f = forwardMostMove;
3817         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3818                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3819                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3820     }
3821     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3822     fprintf(debugFP, "moveNum = %d\n", moveNum);
3823     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3824     setbuf(debugFP, NULL);
3825   }
3826         if (moveNum <= backwardMostMove) {
3827             /* We don't know what the board looked like before
3828                this move.  Punt. */
3829             strcpy(parseList[moveNum - 1], move_str);
3830             strcat(parseList[moveNum - 1], " ");
3831             strcat(parseList[moveNum - 1], elapsed_time);
3832             moveList[moveNum - 1][0] = NULLCHAR;
3833         } else if (strcmp(move_str, "none") == 0) {
3834             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3835             /* Again, we don't know what the board looked like;
3836                this is really the start of the game. */
3837             parseList[moveNum - 1][0] = NULLCHAR;
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             backwardMostMove = moveNum;
3840             startedFromSetupPosition = TRUE;
3841             fromX = fromY = toX = toY = -1;
3842         } else {
3843           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3844           //                 So we parse the long-algebraic move string in stead of the SAN move
3845           int valid; char buf[MSG_SIZ], *prom;
3846
3847           // str looks something like "Q/a1-a2"; kill the slash
3848           if(str[1] == '/') 
3849                 sprintf(buf, "%c%s", str[0], str+2);
3850           else  strcpy(buf, str); // might be castling
3851           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3852                 strcat(buf, prom); // long move lacks promo specification!
3853           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3854                 if(appData.debugMode) 
3855                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3856                 strcpy(move_str, buf);
3857           }
3858           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3859                                 &fromX, &fromY, &toX, &toY, &promoChar)
3860                || ParseOneMove(buf, moveNum - 1, &moveType,
3861                                 &fromX, &fromY, &toX, &toY, &promoChar);
3862           // end of long SAN patch
3863           if (valid) {
3864             (void) CoordsToAlgebraic(boards[moveNum - 1],
3865                                      PosFlags(moveNum - 1),
3866                                      fromY, fromX, toY, toX, promoChar,
3867                                      parseList[moveNum-1]);
3868             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3869               case MT_NONE:
3870               case MT_STALEMATE:
3871               default:
3872                 break;
3873               case MT_CHECK:
3874                 if(gameInfo.variant != VariantShogi)
3875                     strcat(parseList[moveNum - 1], "+");
3876                 break;
3877               case MT_CHECKMATE:
3878               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3879                 strcat(parseList[moveNum - 1], "#");
3880                 break;
3881             }
3882             strcat(parseList[moveNum - 1], " ");
3883             strcat(parseList[moveNum - 1], elapsed_time);
3884             /* currentMoveString is set as a side-effect of ParseOneMove */
3885             strcpy(moveList[moveNum - 1], currentMoveString);
3886             strcat(moveList[moveNum - 1], "\n");
3887           } else {
3888             /* Move from ICS was illegal!?  Punt. */
3889   if (appData.debugMode) {
3890     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3891     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3892   }
3893             strcpy(parseList[moveNum - 1], move_str);
3894             strcat(parseList[moveNum - 1], " ");
3895             strcat(parseList[moveNum - 1], elapsed_time);
3896             moveList[moveNum - 1][0] = NULLCHAR;
3897             fromX = fromY = toX = toY = -1;
3898           }
3899         }
3900   if (appData.debugMode) {
3901     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3902     setbuf(debugFP, NULL);
3903   }
3904
3905 #if ZIPPY
3906         /* Send move to chess program (BEFORE animating it). */
3907         if (appData.zippyPlay && !newGame && newMove && 
3908            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3909
3910             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3911                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3912                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3913                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3914                             move_str);
3915                     DisplayError(str, 0);
3916                 } else {
3917                     if (first.sendTime) {
3918                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3919                     }
3920                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3921                     if (firstMove && !bookHit) {
3922                         firstMove = FALSE;
3923                         if (first.useColors) {
3924                           SendToProgram(gameMode == IcsPlayingWhite ?
3925                                         "white\ngo\n" :
3926                                         "black\ngo\n", &first);
3927                         } else {
3928                           SendToProgram("go\n", &first);
3929                         }
3930                         first.maybeThinking = TRUE;
3931                     }
3932                 }
3933             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3934               if (moveList[moveNum - 1][0] == NULLCHAR) {
3935                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3936                 DisplayError(str, 0);
3937               } else {
3938                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3939                 SendMoveToProgram(moveNum - 1, &first);
3940               }
3941             }
3942         }
3943 #endif
3944     }
3945
3946     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3947         /* If move comes from a remote source, animate it.  If it
3948            isn't remote, it will have already been animated. */
3949         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3950             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3951         }
3952         if (!pausing && appData.highlightLastMove) {
3953             SetHighlights(fromX, fromY, toX, toY);
3954         }
3955     }
3956     
3957     /* Start the clocks */
3958     whiteFlag = blackFlag = FALSE;
3959     appData.clockMode = !(basetime == 0 && increment == 0);
3960     if (ticking == 0) {
3961       ics_clock_paused = TRUE;
3962       StopClocks();
3963     } else if (ticking == 1) {
3964       ics_clock_paused = FALSE;
3965     }
3966     if (gameMode == IcsIdle ||
3967         relation == RELATION_OBSERVING_STATIC ||
3968         relation == RELATION_EXAMINING ||
3969         ics_clock_paused)
3970       DisplayBothClocks();
3971     else
3972       StartClocks();
3973     
3974     /* Display opponents and material strengths */
3975     if (gameInfo.variant != VariantBughouse &&
3976         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3977         if (tinyLayout || smallLayout) {
3978             if(gameInfo.variant == VariantNormal)
3979                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3981                     basetime, increment);
3982             else
3983                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3984                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3985                     basetime, increment, (int) gameInfo.variant);
3986         } else {
3987             if(gameInfo.variant == VariantNormal)
3988                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3989                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3990                     basetime, increment);
3991             else
3992                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3994                     basetime, increment, VariantName(gameInfo.variant));
3995         }
3996         DisplayTitle(str);
3997   if (appData.debugMode) {
3998     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3999   }
4000     }
4001
4002    
4003     /* Display the board */
4004     if (!pausing && !appData.noGUI) {
4005       
4006       if (appData.premove)
4007           if (!gotPremove || 
4008              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4009              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4010               ClearPremoveHighlights();
4011
4012       DrawPosition(FALSE, boards[currentMove]);
4013       DisplayMove(moveNum - 1);
4014       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4015             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4016               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4017         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4018       }
4019     }
4020
4021     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4022 #if ZIPPY
4023     if(bookHit) { // [HGM] book: simulate book reply
4024         static char bookMove[MSG_SIZ]; // a bit generous?
4025
4026         programStats.nodes = programStats.depth = programStats.time = 
4027         programStats.score = programStats.got_only_move = 0;
4028         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4029
4030         strcpy(bookMove, "move ");
4031         strcat(bookMove, bookHit);
4032         HandleMachineMove(bookMove, &first);
4033     }
4034 #endif
4035 }
4036
4037 void
4038 GetMoveListEvent()
4039 {
4040     char buf[MSG_SIZ];
4041     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4042         ics_getting_history = H_REQUESTED;
4043         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4044         SendToICS(buf);
4045     }
4046 }
4047
4048 void
4049 AnalysisPeriodicEvent(force)
4050      int force;
4051 {
4052     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4053          && !force) || !appData.periodicUpdates)
4054       return;
4055
4056     /* Send . command to Crafty to collect stats */
4057     SendToProgram(".\n", &first);
4058
4059     /* Don't send another until we get a response (this makes
4060        us stop sending to old Crafty's which don't understand
4061        the "." command (sending illegal cmds resets node count & time,
4062        which looks bad)) */
4063     programStats.ok_to_send = 0;
4064 }
4065
4066 void ics_update_width(new_width)
4067         int new_width;
4068 {
4069         ics_printf("set width %d\n", new_width);
4070 }
4071
4072 void
4073 SendMoveToProgram(moveNum, cps)
4074      int moveNum;
4075      ChessProgramState *cps;
4076 {
4077     char buf[MSG_SIZ];
4078
4079     if (cps->useUsermove) {
4080       SendToProgram("usermove ", cps);
4081     }
4082     if (cps->useSAN) {
4083       char *space;
4084       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4085         int len = space - parseList[moveNum];
4086         memcpy(buf, parseList[moveNum], len);
4087         buf[len++] = '\n';
4088         buf[len] = NULLCHAR;
4089       } else {
4090         sprintf(buf, "%s\n", parseList[moveNum]);
4091       }
4092       SendToProgram(buf, cps);
4093     } else {
4094       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4095         AlphaRank(moveList[moveNum], 4);
4096         SendToProgram(moveList[moveNum], cps);
4097         AlphaRank(moveList[moveNum], 4); // and back
4098       } else
4099       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4100        * the engine. It would be nice to have a better way to identify castle 
4101        * moves here. */
4102       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4103                                                                          && cps->useOOCastle) {
4104         int fromX = moveList[moveNum][0] - AAA; 
4105         int fromY = moveList[moveNum][1] - ONE;
4106         int toX = moveList[moveNum][2] - AAA; 
4107         int toY = moveList[moveNum][3] - ONE;
4108         if((boards[moveNum][fromY][fromX] == WhiteKing 
4109             && boards[moveNum][toY][toX] == WhiteRook)
4110            || (boards[moveNum][fromY][fromX] == BlackKing 
4111                && boards[moveNum][toY][toX] == BlackRook)) {
4112           if(toX > fromX) SendToProgram("O-O\n", cps);
4113           else SendToProgram("O-O-O\n", cps);
4114         }
4115         else SendToProgram(moveList[moveNum], cps);
4116       }
4117       else SendToProgram(moveList[moveNum], cps);
4118       /* End of additions by Tord */
4119     }
4120
4121     /* [HGM] setting up the opening has brought engine in force mode! */
4122     /*       Send 'go' if we are in a mode where machine should play. */
4123     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4124         (gameMode == TwoMachinesPlay   ||
4125 #ifdef ZIPPY
4126          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4127 #endif
4128          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4129         SendToProgram("go\n", cps);
4130   if (appData.debugMode) {
4131     fprintf(debugFP, "(extra)\n");
4132   }
4133     }
4134     setboardSpoiledMachineBlack = 0;
4135 }
4136
4137 void
4138 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4139      ChessMove moveType;
4140      int fromX, fromY, toX, toY;
4141 {
4142     char user_move[MSG_SIZ];
4143
4144     switch (moveType) {
4145       default:
4146         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4147                 (int)moveType, fromX, fromY, toX, toY);
4148         DisplayError(user_move + strlen("say "), 0);
4149         break;
4150       case WhiteKingSideCastle:
4151       case BlackKingSideCastle:
4152       case WhiteQueenSideCastleWild:
4153       case BlackQueenSideCastleWild:
4154       /* PUSH Fabien */
4155       case WhiteHSideCastleFR:
4156       case BlackHSideCastleFR:
4157       /* POP Fabien */
4158         sprintf(user_move, "o-o\n");
4159         break;
4160       case WhiteQueenSideCastle:
4161       case BlackQueenSideCastle:
4162       case WhiteKingSideCastleWild:
4163       case BlackKingSideCastleWild:
4164       /* PUSH Fabien */
4165       case WhiteASideCastleFR:
4166       case BlackASideCastleFR:
4167       /* POP Fabien */
4168         sprintf(user_move, "o-o-o\n");
4169         break;
4170       case WhitePromotionQueen:
4171       case BlackPromotionQueen:
4172       case WhitePromotionRook:
4173       case BlackPromotionRook:
4174       case WhitePromotionBishop:
4175       case BlackPromotionBishop:
4176       case WhitePromotionKnight:
4177       case BlackPromotionKnight:
4178       case WhitePromotionKing:
4179       case BlackPromotionKing:
4180       case WhitePromotionChancellor:
4181       case BlackPromotionChancellor:
4182       case WhitePromotionArchbishop:
4183       case BlackPromotionArchbishop:
4184         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4185             sprintf(user_move, "%c%c%c%c=%c\n",
4186                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4187                 PieceToChar(WhiteFerz));
4188         else if(gameInfo.variant == VariantGreat)
4189             sprintf(user_move, "%c%c%c%c=%c\n",
4190                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191                 PieceToChar(WhiteMan));
4192         else
4193             sprintf(user_move, "%c%c%c%c=%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195                 PieceToChar(PromoPiece(moveType)));
4196         break;
4197       case WhiteDrop:
4198       case BlackDrop:
4199         sprintf(user_move, "%c@%c%c\n",
4200                 ToUpper(PieceToChar((ChessSquare) fromX)),
4201                 AAA + toX, ONE + toY);
4202         break;
4203       case NormalMove:
4204       case WhiteCapturesEnPassant:
4205       case BlackCapturesEnPassant:
4206       case IllegalMove:  /* could be a variant we don't quite understand */
4207         sprintf(user_move, "%c%c%c%c\n",
4208                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4209         break;
4210     }
4211     SendToICS(user_move);
4212     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4213         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4214 }
4215
4216 void
4217 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4218      int rf, ff, rt, ft;
4219      char promoChar;
4220      char move[7];
4221 {
4222     if (rf == DROP_RANK) {
4223         sprintf(move, "%c@%c%c\n",
4224                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4225     } else {
4226         if (promoChar == 'x' || promoChar == NULLCHAR) {
4227             sprintf(move, "%c%c%c%c\n",
4228                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4229         } else {
4230             sprintf(move, "%c%c%c%c%c\n",
4231                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4232         }
4233     }
4234 }
4235
4236 void
4237 ProcessICSInitScript(f)
4238      FILE *f;
4239 {
4240     char buf[MSG_SIZ];
4241
4242     while (fgets(buf, MSG_SIZ, f)) {
4243         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4244     }
4245
4246     fclose(f);
4247 }
4248
4249
4250 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4251 void
4252 AlphaRank(char *move, int n)
4253 {
4254 //    char *p = move, c; int x, y;
4255
4256     if (appData.debugMode) {
4257         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4258     }
4259
4260     if(move[1]=='*' && 
4261        move[2]>='0' && move[2]<='9' &&
4262        move[3]>='a' && move[3]<='x'    ) {
4263         move[1] = '@';
4264         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4265         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4266     } else
4267     if(move[0]>='0' && move[0]<='9' &&
4268        move[1]>='a' && move[1]<='x' &&
4269        move[2]>='0' && move[2]<='9' &&
4270        move[3]>='a' && move[3]<='x'    ) {
4271         /* input move, Shogi -> normal */
4272         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4273         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4274         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4275         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4276     } else
4277     if(move[1]=='@' &&
4278        move[3]>='0' && move[3]<='9' &&
4279        move[2]>='a' && move[2]<='x'    ) {
4280         move[1] = '*';
4281         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4282         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4283     } else
4284     if(
4285        move[0]>='a' && move[0]<='x' &&
4286        move[3]>='0' && move[3]<='9' &&
4287        move[2]>='a' && move[2]<='x'    ) {
4288          /* output move, normal -> Shogi */
4289         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4290         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4291         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4292         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4293         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4294     }
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "   out = '%s'\n", move);
4297     }
4298 }
4299
4300 /* Parser for moves from gnuchess, ICS, or user typein box */
4301 Boolean
4302 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4303      char *move;
4304      int moveNum;
4305      ChessMove *moveType;
4306      int *fromX, *fromY, *toX, *toY;
4307      char *promoChar;
4308 {       
4309     if (appData.debugMode) {
4310         fprintf(debugFP, "move to parse: %s\n", move);
4311     }
4312     *moveType = yylexstr(moveNum, move);
4313
4314     switch (*moveType) {
4315       case WhitePromotionChancellor:
4316       case BlackPromotionChancellor:
4317       case WhitePromotionArchbishop:
4318       case BlackPromotionArchbishop:
4319       case WhitePromotionQueen:
4320       case BlackPromotionQueen:
4321       case WhitePromotionRook:
4322       case BlackPromotionRook:
4323       case WhitePromotionBishop:
4324       case BlackPromotionBishop:
4325       case WhitePromotionKnight:
4326       case BlackPromotionKnight:
4327       case WhitePromotionKing:
4328       case BlackPromotionKing:
4329       case NormalMove:
4330       case WhiteCapturesEnPassant:
4331       case BlackCapturesEnPassant:
4332       case WhiteKingSideCastle:
4333       case WhiteQueenSideCastle:
4334       case BlackKingSideCastle:
4335       case BlackQueenSideCastle:
4336       case WhiteKingSideCastleWild:
4337       case WhiteQueenSideCastleWild:
4338       case BlackKingSideCastleWild:
4339       case BlackQueenSideCastleWild:
4340       /* Code added by Tord: */
4341       case WhiteHSideCastleFR:
4342       case WhiteASideCastleFR:
4343       case BlackHSideCastleFR:
4344       case BlackASideCastleFR:
4345       /* End of code added by Tord */
4346       case IllegalMove:         /* bug or odd chess variant */
4347         *fromX = currentMoveString[0] - AAA;
4348         *fromY = currentMoveString[1] - ONE;
4349         *toX = currentMoveString[2] - AAA;
4350         *toY = currentMoveString[3] - ONE;
4351         *promoChar = currentMoveString[4];
4352         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4353             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4354     if (appData.debugMode) {
4355         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4356     }
4357             *fromX = *fromY = *toX = *toY = 0;
4358             return FALSE;
4359         }
4360         if (appData.testLegality) {
4361           return (*moveType != IllegalMove);
4362         } else {
4363           return !(fromX == fromY && toX == toY);
4364         }
4365
4366       case WhiteDrop:
4367       case BlackDrop:
4368         *fromX = *moveType == WhiteDrop ?
4369           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4370           (int) CharToPiece(ToLower(currentMoveString[0]));
4371         *fromY = DROP_RANK;
4372         *toX = currentMoveString[2] - AAA;
4373         *toY = currentMoveString[3] - ONE;
4374         *promoChar = NULLCHAR;
4375         return TRUE;
4376
4377       case AmbiguousMove:
4378       case ImpossibleMove:
4379       case (ChessMove) 0:       /* end of file */
4380       case ElapsedTime:
4381       case Comment:
4382       case PGNTag:
4383       case NAG:
4384       case WhiteWins:
4385       case BlackWins:
4386       case GameIsDrawn:
4387       default:
4388     if (appData.debugMode) {
4389         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4390     }
4391         /* bug? */
4392         *fromX = *fromY = *toX = *toY = 0;
4393         *promoChar = NULLCHAR;
4394         return FALSE;
4395     }
4396 }
4397
4398 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4399 // All positions will have equal probability, but the current method will not provide a unique
4400 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4401 #define DARK 1
4402 #define LITE 2
4403 #define ANY 3
4404
4405 int squaresLeft[4];
4406 int piecesLeft[(int)BlackPawn];
4407 int seed, nrOfShuffles;
4408
4409 void GetPositionNumber()
4410 {       // sets global variable seed
4411         int i;
4412
4413         seed = appData.defaultFrcPosition;
4414         if(seed < 0) { // randomize based on time for negative FRC position numbers
4415                 for(i=0; i<50; i++) seed += random();
4416                 seed = random() ^ random() >> 8 ^ random() << 8;
4417                 if(seed<0) seed = -seed;
4418         }
4419 }
4420
4421 int put(Board board, int pieceType, int rank, int n, int shade)
4422 // put the piece on the (n-1)-th empty squares of the given shade
4423 {
4424         int i;
4425
4426         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4427                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4428                         board[rank][i] = (ChessSquare) pieceType;
4429                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4430                         squaresLeft[ANY]--;
4431                         piecesLeft[pieceType]--; 
4432                         return i;
4433                 }
4434         }
4435         return -1;
4436 }
4437
4438
4439 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4440 // calculate where the next piece goes, (any empty square), and put it there
4441 {
4442         int i;
4443
4444         i = seed % squaresLeft[shade];
4445         nrOfShuffles *= squaresLeft[shade];
4446         seed /= squaresLeft[shade];
4447         put(board, pieceType, rank, i, shade);
4448 }
4449
4450 void AddTwoPieces(Board board, int pieceType, int rank)
4451 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4452 {
4453         int i, n=squaresLeft[ANY], j=n-1, k;
4454
4455         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4456         i = seed % k;  // pick one
4457         nrOfShuffles *= k;
4458         seed /= k;
4459         while(i >= j) i -= j--;
4460         j = n - 1 - j; i += j;
4461         put(board, pieceType, rank, j, ANY);
4462         put(board, pieceType, rank, i, ANY);
4463 }
4464
4465 void SetUpShuffle(Board board, int number)
4466 {
4467         int i, p, first=1;
4468
4469         GetPositionNumber(); nrOfShuffles = 1;
4470
4471         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4472         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4473         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4474
4475         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4476
4477         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4478             p = (int) board[0][i];
4479             if(p < (int) BlackPawn) piecesLeft[p] ++;
4480             board[0][i] = EmptySquare;
4481         }
4482
4483         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4484             // shuffles restricted to allow normal castling put KRR first
4485             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4486                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4487             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4488                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4489             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4490                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4491             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4492                 put(board, WhiteRook, 0, 0, ANY);
4493             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4494         }
4495
4496         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4497             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4498             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4499                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4500                 while(piecesLeft[p] >= 2) {
4501                     AddOnePiece(board, p, 0, LITE);
4502                     AddOnePiece(board, p, 0, DARK);
4503                 }
4504                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4505             }
4506
4507         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4508             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4509             // but we leave King and Rooks for last, to possibly obey FRC restriction
4510             if(p == (int)WhiteRook) continue;
4511             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4512             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4513         }
4514
4515         // now everything is placed, except perhaps King (Unicorn) and Rooks
4516
4517         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4518             // Last King gets castling rights
4519             while(piecesLeft[(int)WhiteUnicorn]) {
4520                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4521                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4522             }
4523
4524             while(piecesLeft[(int)WhiteKing]) {
4525                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4526                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4527             }
4528
4529
4530         } else {
4531             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4532             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4533         }
4534
4535         // Only Rooks can be left; simply place them all
4536         while(piecesLeft[(int)WhiteRook]) {
4537                 i = put(board, WhiteRook, 0, 0, ANY);
4538                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4539                         if(first) {
4540                                 first=0;
4541                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4542                         }
4543                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4544                 }
4545         }
4546         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4547             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4548         }
4549
4550         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4551 }
4552
4553 int SetCharTable( char *table, const char * map )
4554 /* [HGM] moved here from winboard.c because of its general usefulness */
4555 /*       Basically a safe strcpy that uses the last character as King */
4556 {
4557     int result = FALSE; int NrPieces;
4558
4559     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4560                     && NrPieces >= 12 && !(NrPieces&1)) {
4561         int i; /* [HGM] Accept even length from 12 to 34 */
4562
4563         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4564         for( i=0; i<NrPieces/2-1; i++ ) {
4565             table[i] = map[i];
4566             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4567         }
4568         table[(int) WhiteKing]  = map[NrPieces/2-1];
4569         table[(int) BlackKing]  = map[NrPieces-1];
4570
4571         result = TRUE;
4572     }
4573
4574     return result;
4575 }
4576
4577 void Prelude(Board board)
4578 {       // [HGM] superchess: random selection of exo-pieces
4579         int i, j, k; ChessSquare p; 
4580         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4581
4582         GetPositionNumber(); // use FRC position number
4583
4584         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4585             SetCharTable(pieceToChar, appData.pieceToCharTable);
4586             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4587                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4588         }
4589
4590         j = seed%4;                 seed /= 4; 
4591         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4592         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4593         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4594         j = seed%3 + (seed%3 >= j); seed /= 3; 
4595         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4596         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4597         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4598         j = seed%3;                 seed /= 3; 
4599         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4600         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4601         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4602         j = seed%2 + (seed%2 >= j); seed /= 2; 
4603         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4604         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4605         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4606         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4607         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4608         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4609         put(board, exoPieces[0],    0, 0, ANY);
4610         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4611 }
4612
4613 void
4614 InitPosition(redraw)
4615      int redraw;
4616 {
4617     ChessSquare (* pieces)[BOARD_FILES];
4618     int i, j, pawnRow, overrule,
4619     oldx = gameInfo.boardWidth,
4620     oldy = gameInfo.boardHeight,
4621     oldh = gameInfo.holdingsWidth,
4622     oldv = gameInfo.variant;
4623
4624     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4625
4626     /* [AS] Initialize pv info list [HGM] and game status */
4627     {
4628         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4629             pvInfoList[i].depth = 0;
4630             boards[i][EP_STATUS] = EP_NONE;
4631             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4632         }
4633
4634         initialRulePlies = 0; /* 50-move counter start */
4635
4636         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4637         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4638     }
4639
4640     
4641     /* [HGM] logic here is completely changed. In stead of full positions */
4642     /* the initialized data only consist of the two backranks. The switch */
4643     /* selects which one we will use, which is than copied to the Board   */
4644     /* initialPosition, which for the rest is initialized by Pawns and    */
4645     /* empty squares. This initial position is then copied to boards[0],  */
4646     /* possibly after shuffling, so that it remains available.            */
4647
4648     gameInfo.holdingsWidth = 0; /* default board sizes */
4649     gameInfo.boardWidth    = 8;
4650     gameInfo.boardHeight   = 8;
4651     gameInfo.holdingsSize  = 0;
4652     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4653     for(i=0; i<BOARD_FILES-2; i++)
4654       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4655     initialPosition[EP_STATUS] = EP_NONE;
4656     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4657
4658     switch (gameInfo.variant) {
4659     case VariantFischeRandom:
4660       shuffleOpenings = TRUE;
4661     default:
4662       pieces = FIDEArray;
4663       break;
4664     case VariantShatranj:
4665       pieces = ShatranjArray;
4666       nrCastlingRights = 0;
4667       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4668       break;
4669     case VariantTwoKings:
4670       pieces = twoKingsArray;
4671       break;
4672     case VariantCapaRandom:
4673       shuffleOpenings = TRUE;
4674     case VariantCapablanca:
4675       pieces = CapablancaArray;
4676       gameInfo.boardWidth = 10;
4677       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4678       break;
4679     case VariantGothic:
4680       pieces = GothicArray;
4681       gameInfo.boardWidth = 10;
4682       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4683       break;
4684     case VariantJanus:
4685       pieces = JanusArray;
4686       gameInfo.boardWidth = 10;
4687       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4688       nrCastlingRights = 6;
4689         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4690         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4691         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4692         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4693         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4694         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4695       break;
4696     case VariantFalcon:
4697       pieces = FalconArray;
4698       gameInfo.boardWidth = 10;
4699       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4700       break;
4701     case VariantXiangqi:
4702       pieces = XiangqiArray;
4703       gameInfo.boardWidth  = 9;
4704       gameInfo.boardHeight = 10;
4705       nrCastlingRights = 0;
4706       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4707       break;
4708     case VariantShogi:
4709       pieces = ShogiArray;
4710       gameInfo.boardWidth  = 9;
4711       gameInfo.boardHeight = 9;
4712       gameInfo.holdingsSize = 7;
4713       nrCastlingRights = 0;
4714       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4715       break;
4716     case VariantCourier:
4717       pieces = CourierArray;
4718       gameInfo.boardWidth  = 12;
4719       nrCastlingRights = 0;
4720       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4721       break;
4722     case VariantKnightmate:
4723       pieces = KnightmateArray;
4724       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4725       break;
4726     case VariantFairy:
4727       pieces = fairyArray;
4728       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4729       break;
4730     case VariantGreat:
4731       pieces = GreatArray;
4732       gameInfo.boardWidth = 10;
4733       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4734       gameInfo.holdingsSize = 8;
4735       break;
4736     case VariantSuper:
4737       pieces = FIDEArray;
4738       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4739       gameInfo.holdingsSize = 8;
4740       startedFromSetupPosition = TRUE;
4741       break;
4742     case VariantCrazyhouse:
4743     case VariantBughouse:
4744       pieces = FIDEArray;
4745       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4746       gameInfo.holdingsSize = 5;
4747       break;
4748     case VariantWildCastle:
4749       pieces = FIDEArray;
4750       /* !!?shuffle with kings guaranteed to be on d or e file */
4751       shuffleOpenings = 1;
4752       break;
4753     case VariantNoCastle:
4754       pieces = FIDEArray;
4755       nrCastlingRights = 0;
4756       /* !!?unconstrained back-rank shuffle */
4757       shuffleOpenings = 1;
4758       break;
4759     }
4760
4761     overrule = 0;
4762     if(appData.NrFiles >= 0) {
4763         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4764         gameInfo.boardWidth = appData.NrFiles;
4765     }
4766     if(appData.NrRanks >= 0) {
4767         gameInfo.boardHeight = appData.NrRanks;
4768     }
4769     if(appData.holdingsSize >= 0) {
4770         i = appData.holdingsSize;
4771         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4772         gameInfo.holdingsSize = i;
4773     }
4774     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4775     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4776         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4777
4778     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4779     if(pawnRow < 1) pawnRow = 1;
4780
4781     /* User pieceToChar list overrules defaults */
4782     if(appData.pieceToCharTable != NULL)
4783         SetCharTable(pieceToChar, appData.pieceToCharTable);
4784
4785     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4786
4787         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4788             s = (ChessSquare) 0; /* account holding counts in guard band */
4789         for( i=0; i<BOARD_HEIGHT; i++ )
4790             initialPosition[i][j] = s;
4791
4792         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4793         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4794         initialPosition[pawnRow][j] = WhitePawn;
4795         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4796         if(gameInfo.variant == VariantXiangqi) {
4797             if(j&1) {
4798                 initialPosition[pawnRow][j] = 
4799                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4800                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4801                    initialPosition[2][j] = WhiteCannon;
4802                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4803                 }
4804             }
4805         }
4806         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4807     }
4808     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4809
4810             j=BOARD_LEFT+1;
4811             initialPosition[1][j] = WhiteBishop;
4812             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4813             j=BOARD_RGHT-2;
4814             initialPosition[1][j] = WhiteRook;
4815             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4816     }
4817
4818     if( nrCastlingRights == -1) {
4819         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4820         /*       This sets default castling rights from none to normal corners   */
4821         /* Variants with other castling rights must set them themselves above    */
4822         nrCastlingRights = 6;
4823        
4824         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4825         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4826         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4827         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4828         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4829         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4830      }
4831
4832      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4833      if(gameInfo.variant == VariantGreat) { // promotion commoners
4834         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4835         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4836         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4837         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4838      }
4839   if (appData.debugMode) {
4840     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4841   }
4842     if(shuffleOpenings) {
4843         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4844         startedFromSetupPosition = TRUE;
4845     }
4846     if(startedFromPositionFile) {
4847       /* [HGM] loadPos: use PositionFile for every new game */
4848       CopyBoard(initialPosition, filePosition);
4849       for(i=0; i<nrCastlingRights; i++)
4850           initialRights[i] = filePosition[CASTLING][i];
4851       startedFromSetupPosition = TRUE;
4852     }
4853
4854     CopyBoard(boards[0], initialPosition);
4855
4856     if(oldx != gameInfo.boardWidth ||
4857        oldy != gameInfo.boardHeight ||
4858        oldh != gameInfo.holdingsWidth
4859 #ifdef GOTHIC
4860        || oldv == VariantGothic ||        // For licensing popups
4861        gameInfo.variant == VariantGothic
4862 #endif
4863 #ifdef FALCON
4864        || oldv == VariantFalcon ||
4865        gameInfo.variant == VariantFalcon
4866 #endif
4867                                          )
4868             InitDrawingSizes(-2 ,0);
4869
4870     if (redraw)
4871       DrawPosition(TRUE, boards[currentMove]);
4872 }
4873
4874 void
4875 SendBoard(cps, moveNum)
4876      ChessProgramState *cps;
4877      int moveNum;
4878 {
4879     char message[MSG_SIZ];
4880     
4881     if (cps->useSetboard) {
4882       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4883       sprintf(message, "setboard %s\n", fen);
4884       SendToProgram(message, cps);
4885       free(fen);
4886
4887     } else {
4888       ChessSquare *bp;
4889       int i, j;
4890       /* Kludge to set black to move, avoiding the troublesome and now
4891        * deprecated "black" command.
4892        */
4893       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4894
4895       SendToProgram("edit\n", cps);
4896       SendToProgram("#\n", cps);
4897       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4898         bp = &boards[moveNum][i][BOARD_LEFT];
4899         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4900           if ((int) *bp < (int) BlackPawn) {
4901             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4902                     AAA + j, ONE + i);
4903             if(message[0] == '+' || message[0] == '~') {
4904                 sprintf(message, "%c%c%c+\n",
4905                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4906                         AAA + j, ONE + i);
4907             }
4908             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4909                 message[1] = BOARD_RGHT   - 1 - j + '1';
4910                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4911             }
4912             SendToProgram(message, cps);
4913           }
4914         }
4915       }
4916     
4917       SendToProgram("c\n", cps);
4918       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4919         bp = &boards[moveNum][i][BOARD_LEFT];
4920         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4921           if (((int) *bp != (int) EmptySquare)
4922               && ((int) *bp >= (int) BlackPawn)) {
4923             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4924                     AAA + j, ONE + i);
4925             if(message[0] == '+' || message[0] == '~') {
4926                 sprintf(message, "%c%c%c+\n",
4927                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4928                         AAA + j, ONE + i);
4929             }
4930             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4931                 message[1] = BOARD_RGHT   - 1 - j + '1';
4932                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4933             }
4934             SendToProgram(message, cps);
4935           }
4936         }
4937       }
4938     
4939       SendToProgram(".\n", cps);
4940     }
4941     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4942 }
4943
4944 int
4945 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4946 {
4947     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4948     /* [HGM] add Shogi promotions */
4949     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4950     ChessSquare piece;
4951     ChessMove moveType;
4952     Boolean premove;
4953
4954     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4955     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4956
4957     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4958       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4959         return FALSE;
4960
4961     piece = boards[currentMove][fromY][fromX];
4962     if(gameInfo.variant == VariantShogi) {
4963         promotionZoneSize = 3;
4964         highestPromotingPiece = (int)WhiteFerz;
4965     }
4966
4967     // next weed out all moves that do not touch the promotion zone at all
4968     if((int)piece >= BlackPawn) {
4969         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4970              return FALSE;
4971         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4972     } else {
4973         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4974            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4975     }
4976
4977     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4978
4979     // weed out mandatory Shogi promotions
4980     if(gameInfo.variant == VariantShogi) {
4981         if(piece >= BlackPawn) {
4982             if(toY == 0 && piece == BlackPawn ||
4983                toY == 0 && piece == BlackQueen ||
4984                toY <= 1 && piece == BlackKnight) {
4985                 *promoChoice = '+';
4986                 return FALSE;
4987             }
4988         } else {
4989             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4990                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4991                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4992                 *promoChoice = '+';
4993                 return FALSE;
4994             }
4995         }
4996     }
4997
4998     // weed out obviously illegal Pawn moves
4999     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5000         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5001         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5002         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5003         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5004         // note we are not allowed to test for valid (non-)capture, due to premove
5005     }
5006
5007     // we either have a choice what to promote to, or (in Shogi) whether to promote
5008     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5009         *promoChoice = PieceToChar(BlackFerz);  // no choice
5010         return FALSE;
5011     }
5012     if(appData.alwaysPromoteToQueen) { // predetermined
5013         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5014              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5015         else *promoChoice = PieceToChar(BlackQueen);
5016         return FALSE;
5017     }
5018
5019     // suppress promotion popup on illegal moves that are not premoves
5020     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5021               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5022     if(appData.testLegality && !premove) {
5023         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5024                         fromY, fromX, toY, toX, NULLCHAR);
5025         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5026            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5027             return FALSE;
5028     }
5029
5030     return TRUE;
5031 }
5032
5033 int
5034 InPalace(row, column)
5035      int row, column;
5036 {   /* [HGM] for Xiangqi */
5037     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5038          column < (BOARD_WIDTH + 4)/2 &&
5039          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5040     return FALSE;
5041 }
5042
5043 int
5044 PieceForSquare (x, y)
5045      int x;
5046      int y;
5047 {
5048   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5049      return -1;
5050   else
5051      return boards[currentMove][y][x];
5052 }
5053
5054 int
5055 OKToStartUserMove(x, y)
5056      int x, y;
5057 {
5058     ChessSquare from_piece;
5059     int white_piece;
5060
5061     if (matchMode) return FALSE;
5062     if (gameMode == EditPosition) return TRUE;
5063
5064     if (x >= 0 && y >= 0)
5065       from_piece = boards[currentMove][y][x];
5066     else
5067       from_piece = EmptySquare;
5068
5069     if (from_piece == EmptySquare) return FALSE;
5070
5071     white_piece = (int)from_piece >= (int)WhitePawn &&
5072       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5073
5074     switch (gameMode) {
5075       case PlayFromGameFile:
5076       case AnalyzeFile:
5077       case TwoMachinesPlay:
5078       case EndOfGame:
5079         return FALSE;
5080
5081       case IcsObserving:
5082       case IcsIdle:
5083         return FALSE;
5084
5085       case MachinePlaysWhite:
5086       case IcsPlayingBlack:
5087         if (appData.zippyPlay) return FALSE;
5088         if (white_piece) {
5089             DisplayMoveError(_("You are playing Black"));
5090             return FALSE;
5091         }
5092         break;
5093
5094       case MachinePlaysBlack:
5095       case IcsPlayingWhite:
5096         if (appData.zippyPlay) return FALSE;
5097         if (!white_piece) {
5098             DisplayMoveError(_("You are playing White"));
5099             return FALSE;
5100         }
5101         break;
5102
5103       case EditGame:
5104         if (!white_piece && WhiteOnMove(currentMove)) {
5105             DisplayMoveError(_("It is White's turn"));
5106             return FALSE;
5107         }           
5108         if (white_piece && !WhiteOnMove(currentMove)) {
5109             DisplayMoveError(_("It is Black's turn"));
5110             return FALSE;
5111         }           
5112         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5113             /* Editing correspondence game history */
5114             /* Could disallow this or prompt for confirmation */
5115             cmailOldMove = -1;
5116         }
5117         break;
5118
5119       case BeginningOfGame:
5120         if (appData.icsActive) return FALSE;
5121         if (!appData.noChessProgram) {
5122             if (!white_piece) {
5123                 DisplayMoveError(_("You are playing White"));
5124                 return FALSE;
5125             }
5126         }
5127         break;
5128         
5129       case Training:
5130         if (!white_piece && WhiteOnMove(currentMove)) {
5131             DisplayMoveError(_("It is White's turn"));
5132             return FALSE;
5133         }           
5134         if (white_piece && !WhiteOnMove(currentMove)) {
5135             DisplayMoveError(_("It is Black's turn"));
5136             return FALSE;
5137         }           
5138         break;
5139
5140       default:
5141       case IcsExamining:
5142         break;
5143     }
5144     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5145         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5146         && gameMode != AnalyzeFile && gameMode != Training) {
5147         DisplayMoveError(_("Displayed position is not current"));
5148         return FALSE;
5149     }
5150     return TRUE;
5151 }
5152
5153 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5154 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5155 int lastLoadGameUseList = FALSE;
5156 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5157 ChessMove lastLoadGameStart = (ChessMove) 0;
5158
5159 ChessMove
5160 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5161      int fromX, fromY, toX, toY;
5162      int promoChar;
5163      Boolean captureOwn;
5164 {
5165     ChessMove moveType;
5166     ChessSquare pdown, pup;
5167
5168     /* Check if the user is playing in turn.  This is complicated because we
5169        let the user "pick up" a piece before it is his turn.  So the piece he
5170        tried to pick up may have been captured by the time he puts it down!
5171        Therefore we use the color the user is supposed to be playing in this
5172        test, not the color of the piece that is currently on the starting
5173        square---except in EditGame mode, where the user is playing both
5174        sides; fortunately there the capture race can't happen.  (It can
5175        now happen in IcsExamining mode, but that's just too bad.  The user
5176        will get a somewhat confusing message in that case.)
5177        */
5178
5179     switch (gameMode) {
5180       case PlayFromGameFile:
5181       case AnalyzeFile:
5182       case TwoMachinesPlay:
5183       case EndOfGame:
5184       case IcsObserving:
5185       case IcsIdle:
5186         /* We switched into a game mode where moves are not accepted,
5187            perhaps while the mouse button was down. */
5188         return ImpossibleMove;
5189
5190       case MachinePlaysWhite:
5191         /* User is moving for Black */
5192         if (WhiteOnMove(currentMove)) {
5193             DisplayMoveError(_("It is White's turn"));
5194             return ImpossibleMove;
5195         }
5196         break;
5197
5198       case MachinePlaysBlack:
5199         /* User is moving for White */
5200         if (!WhiteOnMove(currentMove)) {
5201             DisplayMoveError(_("It is Black's turn"));
5202             return ImpossibleMove;
5203         }
5204         break;
5205
5206       case EditGame:
5207       case IcsExamining:
5208       case BeginningOfGame:
5209       case AnalyzeMode:
5210       case Training:
5211         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5212             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5213             /* User is moving for Black */
5214             if (WhiteOnMove(currentMove)) {
5215                 DisplayMoveError(_("It is White's turn"));
5216                 return ImpossibleMove;
5217             }
5218         } else {
5219             /* User is moving for White */
5220             if (!WhiteOnMove(currentMove)) {
5221                 DisplayMoveError(_("It is Black's turn"));
5222                 return ImpossibleMove;
5223             }
5224         }
5225         break;
5226
5227       case IcsPlayingBlack:
5228         /* User is moving for Black */
5229         if (WhiteOnMove(currentMove)) {
5230             if (!appData.premove) {
5231                 DisplayMoveError(_("It is White's turn"));
5232             } else if (toX >= 0 && toY >= 0) {
5233                 premoveToX = toX;
5234                 premoveToY = toY;
5235                 premoveFromX = fromX;
5236                 premoveFromY = fromY;
5237                 premovePromoChar = promoChar;
5238                 gotPremove = 1;
5239                 if (appData.debugMode) 
5240                     fprintf(debugFP, "Got premove: fromX %d,"
5241                             "fromY %d, toX %d, toY %d\n",
5242                             fromX, fromY, toX, toY);
5243             }
5244             return ImpossibleMove;
5245         }
5246         break;
5247
5248       case IcsPlayingWhite:
5249         /* User is moving for White */
5250         if (!WhiteOnMove(currentMove)) {
5251             if (!appData.premove) {
5252                 DisplayMoveError(_("It is Black's turn"));
5253             } else if (toX >= 0 && toY >= 0) {
5254                 premoveToX = toX;
5255                 premoveToY = toY;
5256                 premoveFromX = fromX;
5257                 premoveFromY = fromY;
5258                 premovePromoChar = promoChar;
5259                 gotPremove = 1;
5260                 if (appData.debugMode) 
5261                     fprintf(debugFP, "Got premove: fromX %d,"
5262                             "fromY %d, toX %d, toY %d\n",
5263                             fromX, fromY, toX, toY);
5264             }
5265             return ImpossibleMove;
5266         }
5267         break;
5268
5269       default:
5270         break;
5271
5272       case EditPosition:
5273         /* EditPosition, empty square, or different color piece;
5274            click-click move is possible */
5275         if (toX == -2 || toY == -2) {
5276             boards[0][fromY][fromX] = EmptySquare;
5277             return AmbiguousMove;
5278         } else if (toX >= 0 && toY >= 0) {
5279             boards[0][toY][toX] = boards[0][fromY][fromX];
5280             boards[0][fromY][fromX] = EmptySquare;
5281             return AmbiguousMove;
5282         }
5283         return ImpossibleMove;
5284     }
5285
5286     if(toX < 0 || toY < 0) return ImpossibleMove;
5287     pdown = boards[currentMove][fromY][fromX];
5288     pup = boards[currentMove][toY][toX];
5289
5290     /* [HGM] If move started in holdings, it means a drop */
5291     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5292          if( pup != EmptySquare ) return ImpossibleMove;
5293          if(appData.testLegality) {
5294              /* it would be more logical if LegalityTest() also figured out
5295               * which drops are legal. For now we forbid pawns on back rank.
5296               * Shogi is on its own here...
5297               */
5298              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5299                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5300                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5301          }
5302          return WhiteDrop; /* Not needed to specify white or black yet */
5303     }
5304
5305     userOfferedDraw = FALSE;
5306         
5307     /* [HGM] always test for legality, to get promotion info */
5308     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5309                                          fromY, fromX, toY, toX, promoChar);
5310     /* [HGM] but possibly ignore an IllegalMove result */
5311     if (appData.testLegality) {
5312         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5313             DisplayMoveError(_("Illegal move"));
5314             return ImpossibleMove;
5315         }
5316     }
5317
5318     return moveType;
5319     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5320        function is made into one that returns an OK move type if FinishMove
5321        should be called. This to give the calling driver routine the
5322        opportunity to finish the userMove input with a promotion popup,
5323        without bothering the user with this for invalid or illegal moves */
5324
5325 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5326 }
5327
5328 /* Common tail of UserMoveEvent and DropMenuEvent */
5329 int
5330 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5331      ChessMove moveType;
5332      int fromX, fromY, toX, toY;
5333      /*char*/int promoChar;
5334 {
5335     char *bookHit = 0;
5336
5337     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5338         // [HGM] superchess: suppress promotions to non-available piece
5339         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340         if(WhiteOnMove(currentMove)) {
5341             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5342         } else {
5343             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5344         }
5345     }
5346
5347     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5348        move type in caller when we know the move is a legal promotion */
5349     if(moveType == NormalMove && promoChar)
5350         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5351
5352     /* [HGM] convert drag-and-drop piece drops to standard form */
5353     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5354          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5355            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5356                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5357            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5358            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5359            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5360            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5361          fromY = DROP_RANK;
5362     }
5363
5364     /* [HGM] <popupFix> The following if has been moved here from
5365        UserMoveEvent(). Because it seemed to belong here (why not allow
5366        piece drops in training games?), and because it can only be
5367        performed after it is known to what we promote. */
5368     if (gameMode == Training) {
5369       /* compare the move played on the board to the next move in the
5370        * game. If they match, display the move and the opponent's response. 
5371        * If they don't match, display an error message.
5372        */
5373       int saveAnimate;
5374       Board testBoard;
5375       CopyBoard(testBoard, boards[currentMove]);
5376       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5377
5378       if (CompareBoards(testBoard, boards[currentMove+1])) {
5379         ForwardInner(currentMove+1);
5380
5381         /* Autoplay the opponent's response.
5382          * if appData.animate was TRUE when Training mode was entered,
5383          * the response will be animated.
5384          */
5385         saveAnimate = appData.animate;
5386         appData.animate = animateTraining;
5387         ForwardInner(currentMove+1);
5388         appData.animate = saveAnimate;
5389
5390         /* check for the end of the game */
5391         if (currentMove >= forwardMostMove) {
5392           gameMode = PlayFromGameFile;
5393           ModeHighlight();
5394           SetTrainingModeOff();
5395           DisplayInformation(_("End of game"));
5396         }
5397       } else {
5398         DisplayError(_("Incorrect move"), 0);
5399       }
5400       return 1;
5401     }
5402
5403   /* Ok, now we know that the move is good, so we can kill
5404      the previous line in Analysis Mode */
5405   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5406                                 && currentMove < forwardMostMove) {
5407     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5408   }
5409
5410   /* If we need the chess program but it's dead, restart it */
5411   ResurrectChessProgram();
5412
5413   /* A user move restarts a paused game*/
5414   if (pausing)
5415     PauseEvent();
5416
5417   thinkOutput[0] = NULLCHAR;
5418
5419   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5420
5421   if (gameMode == BeginningOfGame) {
5422     if (appData.noChessProgram) {
5423       gameMode = EditGame;
5424       SetGameInfo();
5425     } else {
5426       char buf[MSG_SIZ];
5427       gameMode = MachinePlaysBlack;
5428       StartClocks();
5429       SetGameInfo();
5430       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5431       DisplayTitle(buf);
5432       if (first.sendName) {
5433         sprintf(buf, "name %s\n", gameInfo.white);
5434         SendToProgram(buf, &first);
5435       }
5436       StartClocks();
5437     }
5438     ModeHighlight();
5439   }
5440
5441   /* Relay move to ICS or chess engine */
5442   if (appData.icsActive) {
5443     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5444         gameMode == IcsExamining) {
5445       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5446       ics_user_moved = 1;
5447     }
5448   } else {
5449     if (first.sendTime && (gameMode == BeginningOfGame ||
5450                            gameMode == MachinePlaysWhite ||
5451                            gameMode == MachinePlaysBlack)) {
5452       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5453     }
5454     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5455          // [HGM] book: if program might be playing, let it use book
5456         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5457         first.maybeThinking = TRUE;
5458     } else SendMoveToProgram(forwardMostMove-1, &first);
5459     if (currentMove == cmailOldMove + 1) {
5460       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5461     }
5462   }
5463
5464   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5465
5466   switch (gameMode) {
5467   case EditGame:
5468     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5469     case MT_NONE:
5470     case MT_CHECK:
5471       break;
5472     case MT_CHECKMATE:
5473     case MT_STAINMATE:
5474       if (WhiteOnMove(currentMove)) {
5475         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5476       } else {
5477         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5478       }
5479       break;
5480     case MT_STALEMATE:
5481       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5482       break;
5483     }
5484     break;
5485     
5486   case MachinePlaysBlack:
5487   case MachinePlaysWhite:
5488     /* disable certain menu options while machine is thinking */
5489     SetMachineThinkingEnables();
5490     break;
5491
5492   default:
5493     break;
5494   }
5495
5496   if(bookHit) { // [HGM] book: simulate book reply
5497         static char bookMove[MSG_SIZ]; // a bit generous?
5498
5499         programStats.nodes = programStats.depth = programStats.time = 
5500         programStats.score = programStats.got_only_move = 0;
5501         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5502
5503         strcpy(bookMove, "move ");
5504         strcat(bookMove, bookHit);
5505         HandleMachineMove(bookMove, &first);
5506   }
5507   return 1;
5508 }
5509
5510 void
5511 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5512      int fromX, fromY, toX, toY;
5513      int promoChar;
5514 {
5515     /* [HGM] This routine was added to allow calling of its two logical
5516        parts from other modules in the old way. Before, UserMoveEvent()
5517        automatically called FinishMove() if the move was OK, and returned
5518        otherwise. I separated the two, in order to make it possible to
5519        slip a promotion popup in between. But that it always needs two
5520        calls, to the first part, (now called UserMoveTest() ), and to
5521        FinishMove if the first part succeeded. Calls that do not need
5522        to do anything in between, can call this routine the old way. 
5523     */
5524     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5525 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5526     if(moveType == AmbiguousMove)
5527         DrawPosition(FALSE, boards[currentMove]);
5528     else if(moveType != ImpossibleMove && moveType != Comment)
5529         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5530 }
5531
5532 void LeftClick(ClickType clickType, int xPix, int yPix)
5533 {
5534     int x, y;
5535     Boolean saveAnimate;
5536     static int second = 0, promotionChoice = 0;
5537     char promoChoice = NULLCHAR;
5538
5539     if (clickType == Press) ErrorPopDown();
5540
5541     x = EventToSquare(xPix, BOARD_WIDTH);
5542     y = EventToSquare(yPix, BOARD_HEIGHT);
5543     if (!flipView && y >= 0) {
5544         y = BOARD_HEIGHT - 1 - y;
5545     }
5546     if (flipView && x >= 0) {
5547         x = BOARD_WIDTH - 1 - x;
5548     }
5549
5550     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5551         if(clickType == Release) return; // ignore upclick of click-click destination
5552         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5553         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5554         if(gameInfo.holdingsWidth && 
5555                 (WhiteOnMove(currentMove) 
5556                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5557                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5558             // click in right holdings, for determining promotion piece
5559             ChessSquare p = boards[currentMove][y][x];
5560             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5561             if(p != EmptySquare) {
5562                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5563                 fromX = fromY = -1;
5564                 return;
5565             }
5566         }
5567         DrawPosition(FALSE, boards[currentMove]);
5568         return;
5569     }
5570
5571     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5572     if(clickType == Press
5573             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5574               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5575               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5576         return;
5577
5578     if (fromX == -1) {
5579         if (clickType == Press) {
5580             /* First square */
5581             if (OKToStartUserMove(x, y)) {
5582                 fromX = x;
5583                 fromY = y;
5584                 second = 0;
5585                 DragPieceBegin(xPix, yPix);
5586                 if (appData.highlightDragging) {
5587                     SetHighlights(x, y, -1, -1);
5588                 }
5589             }
5590         }
5591         return;
5592     }
5593
5594     /* fromX != -1 */
5595     if (clickType == Press && gameMode != EditPosition) {
5596         ChessSquare fromP;
5597         ChessSquare toP;
5598         int frc;
5599
5600         // ignore off-board to clicks
5601         if(y < 0 || x < 0) return;
5602
5603         /* Check if clicking again on the same color piece */
5604         fromP = boards[currentMove][fromY][fromX];
5605         toP = boards[currentMove][y][x];
5606         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5607         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5608              WhitePawn <= toP && toP <= WhiteKing &&
5609              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5610              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5611             (BlackPawn <= fromP && fromP <= BlackKing && 
5612              BlackPawn <= toP && toP <= BlackKing &&
5613              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5614              !(fromP == BlackKing && toP == BlackRook && frc))) {
5615             /* Clicked again on same color piece -- changed his mind */
5616             second = (x == fromX && y == fromY);
5617             if (appData.highlightDragging) {
5618                 SetHighlights(x, y, -1, -1);
5619             } else {
5620                 ClearHighlights();
5621             }
5622             if (OKToStartUserMove(x, y)) {
5623                 fromX = x;
5624                 fromY = y;
5625                 DragPieceBegin(xPix, yPix);
5626             }
5627             return;
5628         }
5629         // ignore clicks on holdings
5630         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5631     }
5632
5633     if (clickType == Release && x == fromX && y == fromY) {
5634         DragPieceEnd(xPix, yPix);
5635         if (appData.animateDragging) {
5636             /* Undo animation damage if any */
5637             DrawPosition(FALSE, NULL);
5638         }
5639         if (second) {
5640             /* Second up/down in same square; just abort move */
5641             second = 0;
5642             fromX = fromY = -1;
5643             ClearHighlights();
5644             gotPremove = 0;
5645             ClearPremoveHighlights();
5646         } else {
5647             /* First upclick in same square; start click-click mode */
5648             SetHighlights(x, y, -1, -1);
5649         }
5650         return;
5651     }
5652
5653     /* we now have a different from- and (possibly off-board) to-square */
5654     /* Completed move */
5655     toX = x;
5656     toY = y;
5657     saveAnimate = appData.animate;
5658     if (clickType == Press) {
5659         /* Finish clickclick move */
5660         if (appData.animate || appData.highlightLastMove) {
5661             SetHighlights(fromX, fromY, toX, toY);
5662         } else {
5663             ClearHighlights();
5664         }
5665     } else {
5666         /* Finish drag move */
5667         if (appData.highlightLastMove) {
5668             SetHighlights(fromX, fromY, toX, toY);
5669         } else {
5670             ClearHighlights();
5671         }
5672         DragPieceEnd(xPix, yPix);
5673         /* Don't animate move and drag both */
5674         appData.animate = FALSE;
5675     }
5676
5677     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5678     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5679         ClearHighlights();
5680         fromX = fromY = -1;
5681         DrawPosition(TRUE, NULL);
5682         return;
5683     }
5684
5685     // off-board moves should not be highlighted
5686     if(x < 0 || x < 0) ClearHighlights();
5687
5688     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5689         SetHighlights(fromX, fromY, toX, toY);
5690         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5691             // [HGM] super: promotion to captured piece selected from holdings
5692             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5693             promotionChoice = TRUE;
5694             // kludge follows to temporarily execute move on display, without promoting yet
5695             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5696             boards[currentMove][toY][toX] = p;
5697             DrawPosition(FALSE, boards[currentMove]);
5698             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5699             boards[currentMove][toY][toX] = q;
5700             DisplayMessage("Click in holdings to choose piece", "");
5701             return;
5702         }
5703         PromotionPopUp();
5704     } else {
5705         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5706         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5707         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5708         fromX = fromY = -1;
5709     }
5710     appData.animate = saveAnimate;
5711     if (appData.animate || appData.animateDragging) {
5712         /* Undo animation damage if needed */
5713         DrawPosition(FALSE, NULL);
5714     }
5715 }
5716
5717 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5718 {
5719 //    char * hint = lastHint;
5720     FrontEndProgramStats stats;
5721
5722     stats.which = cps == &first ? 0 : 1;
5723     stats.depth = cpstats->depth;
5724     stats.nodes = cpstats->nodes;
5725     stats.score = cpstats->score;
5726     stats.time = cpstats->time;
5727     stats.pv = cpstats->movelist;
5728     stats.hint = lastHint;
5729     stats.an_move_index = 0;
5730     stats.an_move_count = 0;
5731
5732     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5733         stats.hint = cpstats->move_name;
5734         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5735         stats.an_move_count = cpstats->nr_moves;
5736     }
5737
5738     SetProgramStats( &stats );
5739 }
5740
5741 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5742 {   // [HGM] book: this routine intercepts moves to simulate book replies
5743     char *bookHit = NULL;
5744
5745     //first determine if the incoming move brings opponent into his book
5746     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5747         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5748     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5749     if(bookHit != NULL && !cps->bookSuspend) {
5750         // make sure opponent is not going to reply after receiving move to book position
5751         SendToProgram("force\n", cps);
5752         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5753     }
5754     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5755     // now arrange restart after book miss
5756     if(bookHit) {
5757         // after a book hit we never send 'go', and the code after the call to this routine
5758         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5759         char buf[MSG_SIZ];
5760         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5761         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5762         SendToProgram(buf, cps);
5763         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5764     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5765         SendToProgram("go\n", cps);
5766         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5767     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5768         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5769             SendToProgram("go\n", cps); 
5770         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5771     }
5772     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5773 }
5774
5775 char *savedMessage;
5776 ChessProgramState *savedState;
5777 void DeferredBookMove(void)
5778 {
5779         if(savedState->lastPing != savedState->lastPong)
5780                     ScheduleDelayedEvent(DeferredBookMove, 10);
5781         else
5782         HandleMachineMove(savedMessage, savedState);
5783 }
5784
5785 void
5786 HandleMachineMove(message, cps)
5787      char *message;
5788      ChessProgramState *cps;
5789 {
5790     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5791     char realname[MSG_SIZ];
5792     int fromX, fromY, toX, toY;
5793     ChessMove moveType;
5794     char promoChar;
5795     char *p;
5796     int machineWhite;
5797     char *bookHit;
5798
5799     cps->userError = 0;
5800
5801 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5802     /*
5803      * Kludge to ignore BEL characters
5804      */
5805     while (*message == '\007') message++;
5806
5807     /*
5808      * [HGM] engine debug message: ignore lines starting with '#' character
5809      */
5810     if(cps->debug && *message == '#') return;
5811
5812     /*
5813      * Look for book output
5814      */
5815     if (cps == &first && bookRequested) {
5816         if (message[0] == '\t' || message[0] == ' ') {
5817             /* Part of the book output is here; append it */
5818             strcat(bookOutput, message);
5819             strcat(bookOutput, "  \n");
5820             return;
5821         } else if (bookOutput[0] != NULLCHAR) {
5822             /* All of book output has arrived; display it */
5823             char *p = bookOutput;
5824             while (*p != NULLCHAR) {
5825                 if (*p == '\t') *p = ' ';
5826                 p++;
5827             }
5828             DisplayInformation(bookOutput);
5829             bookRequested = FALSE;
5830             /* Fall through to parse the current output */
5831         }
5832     }
5833
5834     /*
5835      * Look for machine move.
5836      */
5837     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5838         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5839     {
5840         /* This method is only useful on engines that support ping */
5841         if (cps->lastPing != cps->lastPong) {
5842           if (gameMode == BeginningOfGame) {
5843             /* Extra move from before last new; ignore */
5844             if (appData.debugMode) {
5845                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5846             }
5847           } else {
5848             if (appData.debugMode) {
5849                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5850                         cps->which, gameMode);
5851             }
5852
5853             SendToProgram("undo\n", cps);
5854           }
5855           return;
5856         }
5857
5858         switch (gameMode) {
5859           case BeginningOfGame:
5860             /* Extra move from before last reset; ignore */
5861             if (appData.debugMode) {
5862                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5863             }
5864             return;
5865
5866           case EndOfGame:
5867           case IcsIdle:
5868           default:
5869             /* Extra move after we tried to stop.  The mode test is
5870                not a reliable way of detecting this problem, but it's
5871                the best we can do on engines that don't support ping.
5872             */
5873             if (appData.debugMode) {
5874                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5875                         cps->which, gameMode);
5876             }
5877             SendToProgram("undo\n", cps);
5878             return;
5879
5880           case MachinePlaysWhite:
5881           case IcsPlayingWhite:
5882             machineWhite = TRUE;
5883             break;
5884
5885           case MachinePlaysBlack:
5886           case IcsPlayingBlack:
5887             machineWhite = FALSE;
5888             break;
5889
5890           case TwoMachinesPlay:
5891             machineWhite = (cps->twoMachinesColor[0] == 'w');
5892             break;
5893         }
5894         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5895             if (appData.debugMode) {
5896                 fprintf(debugFP,
5897                         "Ignoring move out of turn by %s, gameMode %d"
5898                         ", forwardMost %d\n",
5899                         cps->which, gameMode, forwardMostMove);
5900             }
5901             return;
5902         }
5903
5904     if (appData.debugMode) { int f = forwardMostMove;
5905         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5906                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5907                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5908     }
5909         if(cps->alphaRank) AlphaRank(machineMove, 4);
5910         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5911                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5912             /* Machine move could not be parsed; ignore it. */
5913             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5914                     machineMove, cps->which);
5915             DisplayError(buf1, 0);
5916             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5917                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5918             if (gameMode == TwoMachinesPlay) {
5919               GameEnds(machineWhite ? BlackWins : WhiteWins,
5920                        buf1, GE_XBOARD);
5921             }
5922             return;
5923         }
5924
5925         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5926         /* So we have to redo legality test with true e.p. status here,  */
5927         /* to make sure an illegal e.p. capture does not slip through,   */
5928         /* to cause a forfeit on a justified illegal-move complaint      */
5929         /* of the opponent.                                              */
5930         if( gameMode==TwoMachinesPlay && appData.testLegality
5931             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5932                                                               ) {
5933            ChessMove moveType;
5934            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5935                              fromY, fromX, toY, toX, promoChar);
5936             if (appData.debugMode) {
5937                 int i;
5938                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5939                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5940                 fprintf(debugFP, "castling rights\n");
5941             }
5942             if(moveType == IllegalMove) {
5943                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5944                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5945                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5946                            buf1, GE_XBOARD);
5947                 return;
5948            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5949            /* [HGM] Kludge to handle engines that send FRC-style castling
5950               when they shouldn't (like TSCP-Gothic) */
5951            switch(moveType) {
5952              case WhiteASideCastleFR:
5953              case BlackASideCastleFR:
5954                toX+=2;
5955                currentMoveString[2]++;
5956                break;
5957              case WhiteHSideCastleFR:
5958              case BlackHSideCastleFR:
5959                toX--;
5960                currentMoveString[2]--;
5961                break;
5962              default: ; // nothing to do, but suppresses warning of pedantic compilers
5963            }
5964         }
5965         hintRequested = FALSE;
5966         lastHint[0] = NULLCHAR;
5967         bookRequested = FALSE;
5968         /* Program may be pondering now */
5969         cps->maybeThinking = TRUE;
5970         if (cps->sendTime == 2) cps->sendTime = 1;
5971         if (cps->offeredDraw) cps->offeredDraw--;
5972
5973 #if ZIPPY
5974         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5975             first.initDone) {
5976           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5977           ics_user_moved = 1;
5978           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5979                 char buf[3*MSG_SIZ];
5980
5981                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5982                         programStats.score / 100.,
5983                         programStats.depth,
5984                         programStats.time / 100.,
5985                         (unsigned int)programStats.nodes,
5986                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5987                         programStats.movelist);
5988                 SendToICS(buf);
5989 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5990           }
5991         }
5992 #endif
5993         /* currentMoveString is set as a side-effect of ParseOneMove */
5994         strcpy(machineMove, currentMoveString);
5995         strcat(machineMove, "\n");
5996         strcpy(moveList[forwardMostMove], machineMove);
5997
5998         /* [AS] Save move info and clear stats for next move */
5999         pvInfoList[ forwardMostMove ].score = programStats.score;
6000         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6001         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6002         ClearProgramStats();
6003         thinkOutput[0] = NULLCHAR;
6004         hiddenThinkOutputState = 0;
6005
6006         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6007
6008         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6009         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6010             int count = 0;
6011
6012             while( count < adjudicateLossPlies ) {
6013                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6014
6015                 if( count & 1 ) {
6016                     score = -score; /* Flip score for winning side */
6017                 }
6018
6019                 if( score > adjudicateLossThreshold ) {
6020                     break;
6021                 }
6022
6023                 count++;
6024             }
6025
6026             if( count >= adjudicateLossPlies ) {
6027                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6028
6029                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6030                     "Xboard adjudication", 
6031                     GE_XBOARD );
6032
6033                 return;
6034             }
6035         }
6036
6037         if( gameMode == TwoMachinesPlay ) {
6038           // [HGM] some adjudications useful with buggy engines
6039             int k, count = 0; static int bare = 1;
6040           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6041
6042
6043             if( appData.testLegality )
6044             {   /* [HGM] Some more adjudications for obstinate engines */
6045                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6046                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6047                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6048                 static int moveCount = 6;
6049                 ChessMove result;
6050                 char *reason = NULL;
6051
6052                 /* Count what is on board. */
6053                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6054                 {   ChessSquare p = boards[forwardMostMove][i][j];
6055                     int m=i;
6056
6057                     switch((int) p)
6058                     {   /* count B,N,R and other of each side */
6059                         case WhiteKing:
6060                         case BlackKing:
6061                              NrK++; break; // [HGM] atomic: count Kings
6062                         case WhiteKnight:
6063                              NrWN++; break;
6064                         case WhiteBishop:
6065                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6066                              bishopsColor |= 1 << ((i^j)&1);
6067                              NrWB++; break;
6068                         case BlackKnight:
6069                              NrBN++; break;
6070                         case BlackBishop:
6071                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6072                              bishopsColor |= 1 << ((i^j)&1);
6073                              NrBB++; break;
6074                         case WhiteRook:
6075                              NrWR++; break;
6076                         case BlackRook:
6077                              NrBR++; break;
6078                         case WhiteQueen:
6079                              NrWQ++; break;
6080                         case BlackQueen:
6081                              NrBQ++; break;
6082                         case EmptySquare: 
6083                              break;
6084                         case BlackPawn:
6085                              m = 7-i;
6086                         case WhitePawn:
6087                              PawnAdvance += m; NrPawns++;
6088                     }
6089                     NrPieces += (p != EmptySquare);
6090                     NrW += ((int)p < (int)BlackPawn);
6091                     if(gameInfo.variant == VariantXiangqi && 
6092                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6093                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6094                         NrW -= ((int)p < (int)BlackPawn);
6095                     }
6096                 }
6097
6098                 /* Some material-based adjudications that have to be made before stalemate test */
6099                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6100                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6101                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6102                      if(appData.checkMates) {
6103                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6104                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6105                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6106                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6107                          return;
6108                      }
6109                 }
6110
6111                 /* Bare King in Shatranj (loses) or Losers (wins) */
6112                 if( NrW == 1 || NrPieces - NrW == 1) {
6113                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6114                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6115                      if(appData.checkMates) {
6116                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6117                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6118                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6119                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6120                          return;
6121                      }
6122                   } else
6123                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6124                   {    /* bare King */
6125                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6126                         if(appData.checkMates) {
6127                             /* but only adjudicate if adjudication enabled */
6128                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6129                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6130                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6131                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6132                             return;
6133                         }
6134                   }
6135                 } else bare = 1;
6136
6137
6138             // don't wait for engine to announce game end if we can judge ourselves
6139             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6140               case MT_CHECK:
6141                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6142                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6143                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6144                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6145                             checkCnt++;
6146                         if(checkCnt >= 2) {
6147                             reason = "Xboard adjudication: 3rd check";
6148                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6149                             break;
6150                         }
6151                     }
6152                 }
6153               case MT_NONE:
6154               default:
6155                 break;
6156               case MT_STALEMATE:
6157               case MT_STAINMATE:
6158                 reason = "Xboard adjudication: Stalemate";
6159                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6160                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6161                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6162                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6163                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6164                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6165                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6166                                                                         EP_CHECKMATE : EP_WINS);
6167                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6168                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6169                 }
6170                 break;
6171               case MT_CHECKMATE:
6172                 reason = "Xboard adjudication: Checkmate";
6173                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6174                 break;
6175             }
6176
6177                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6178                     case EP_STALEMATE:
6179                         result = GameIsDrawn; break;
6180                     case EP_CHECKMATE:
6181                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6182                     case EP_WINS:
6183                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6184                     default:
6185                         result = (ChessMove) 0;
6186                 }
6187                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6188                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6189                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6190                     GameEnds( result, reason, GE_XBOARD );
6191                     return;
6192                 }
6193
6194                 /* Next absolutely insufficient mating material. */
6195                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6196                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6197                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6198                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6199                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6200
6201                      /* always flag draws, for judging claims */
6202                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6203
6204                      if(appData.materialDraws) {
6205                          /* but only adjudicate them if adjudication enabled */
6206                          SendToProgram("force\n", cps->other); // suppress reply
6207                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6208                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6209                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6210                          return;
6211                      }
6212                 }
6213
6214                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6215                 if(NrPieces == 4 && 
6216                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6217                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6218                    || NrWN==2 || NrBN==2     /* KNNK */
6219                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6220                   ) ) {
6221                      if(--moveCount < 0 && appData.trivialDraws)
6222                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6223                           SendToProgram("force\n", cps->other); // suppress reply
6224                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6225                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6226                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6227                           return;
6228                      }
6229                 } else moveCount = 6;
6230             }
6231           }
6232           
6233           if (appData.debugMode) { int i;
6234             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6235                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6236                     appData.drawRepeats);
6237             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6238               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6239             
6240           }
6241
6242                 /* Check for rep-draws */
6243                 count = 0;
6244                 for(k = forwardMostMove-2;
6245                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6246                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6247                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6248                     k-=2)
6249                 {   int rights=0;
6250                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6251                         /* compare castling rights */
6252                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6253                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6254                                 rights++; /* King lost rights, while rook still had them */
6255                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6256                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6257                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6258                                    rights++; /* but at least one rook lost them */
6259                         }
6260                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6261                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6262                                 rights++; 
6263                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6264                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6265                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6266                                    rights++;
6267                         }
6268                         if( rights == 0 && ++count > appData.drawRepeats-2
6269                             && appData.drawRepeats > 1) {
6270                              /* adjudicate after user-specified nr of repeats */
6271                              SendToProgram("force\n", cps->other); // suppress reply
6272                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6273                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6274                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6275                                 // [HGM] xiangqi: check for forbidden perpetuals
6276                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6277                                 for(m=forwardMostMove; m>k; m-=2) {
6278                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6279                                         ourPerpetual = 0; // the current mover did not always check
6280                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6281                                         hisPerpetual = 0; // the opponent did not always check
6282                                 }
6283                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6284                                                                         ourPerpetual, hisPerpetual);
6285                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6286                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6287                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6288                                     return;
6289                                 }
6290                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6291                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6292                                 // Now check for perpetual chases
6293                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6294                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6295                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6296                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6297                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6298                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6299                                         return;
6300                                     }
6301                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6302                                         break; // Abort repetition-checking loop.
6303                                 }
6304                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6305                              }
6306                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6307                              return;
6308                         }
6309                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6310                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6311                     }
6312                 }
6313
6314                 /* Now we test for 50-move draws. Determine ply count */
6315                 count = forwardMostMove;
6316                 /* look for last irreversble move */
6317                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6318                     count--;
6319                 /* if we hit starting position, add initial plies */
6320                 if( count == backwardMostMove )
6321                     count -= initialRulePlies;
6322                 count = forwardMostMove - count; 
6323                 if( count >= 100)
6324                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6325                          /* this is used to judge if draw claims are legal */
6326                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6327                          SendToProgram("force\n", cps->other); // suppress reply
6328                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6329                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6330                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6331                          return;
6332                 }
6333
6334                 /* if draw offer is pending, treat it as a draw claim
6335                  * when draw condition present, to allow engines a way to
6336                  * claim draws before making their move to avoid a race
6337                  * condition occurring after their move
6338                  */
6339                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6340                          char *p = NULL;
6341                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6342                              p = "Draw claim: 50-move rule";
6343                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6344                              p = "Draw claim: 3-fold repetition";
6345                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6346                              p = "Draw claim: insufficient mating material";
6347                          if( p != NULL ) {
6348                              SendToProgram("force\n", cps->other); // suppress reply
6349                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6350                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6351                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6352                              return;
6353                          }
6354                 }
6355
6356
6357                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6358                     SendToProgram("force\n", cps->other); // suppress reply
6359                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6360                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6361
6362                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6363
6364                     return;
6365                 }
6366         }
6367
6368         bookHit = NULL;
6369         if (gameMode == TwoMachinesPlay) {
6370             /* [HGM] relaying draw offers moved to after reception of move */
6371             /* and interpreting offer as claim if it brings draw condition */
6372             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6373                 SendToProgram("draw\n", cps->other);
6374             }
6375             if (cps->other->sendTime) {
6376                 SendTimeRemaining(cps->other,
6377                                   cps->other->twoMachinesColor[0] == 'w');
6378             }
6379             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6380             if (firstMove && !bookHit) {
6381                 firstMove = FALSE;
6382                 if (cps->other->useColors) {
6383                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6384                 }
6385                 SendToProgram("go\n", cps->other);
6386             }
6387             cps->other->maybeThinking = TRUE;
6388         }
6389
6390         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6391         
6392         if (!pausing && appData.ringBellAfterMoves) {
6393             RingBell();
6394         }
6395
6396         /* 
6397          * Reenable menu items that were disabled while
6398          * machine was thinking
6399          */
6400         if (gameMode != TwoMachinesPlay)
6401             SetUserThinkingEnables();
6402
6403         // [HGM] book: after book hit opponent has received move and is now in force mode
6404         // force the book reply into it, and then fake that it outputted this move by jumping
6405         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6406         if(bookHit) {
6407                 static char bookMove[MSG_SIZ]; // a bit generous?
6408
6409                 strcpy(bookMove, "move ");
6410                 strcat(bookMove, bookHit);
6411                 message = bookMove;
6412                 cps = cps->other;
6413                 programStats.nodes = programStats.depth = programStats.time = 
6414                 programStats.score = programStats.got_only_move = 0;
6415                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6416
6417                 if(cps->lastPing != cps->lastPong) {
6418                     savedMessage = message; // args for deferred call
6419                     savedState = cps;
6420                     ScheduleDelayedEvent(DeferredBookMove, 10);
6421                     return;
6422                 }
6423                 goto FakeBookMove;
6424         }
6425
6426         return;
6427     }
6428
6429     /* Set special modes for chess engines.  Later something general
6430      *  could be added here; for now there is just one kludge feature,
6431      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6432      *  when "xboard" is given as an interactive command.
6433      */
6434     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6435         cps->useSigint = FALSE;
6436         cps->useSigterm = FALSE;
6437     }
6438     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6439       ParseFeatures(message+8, cps);
6440       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6441     }
6442
6443     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6444      * want this, I was asked to put it in, and obliged.
6445      */
6446     if (!strncmp(message, "setboard ", 9)) {
6447         Board initial_position;
6448
6449         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6450
6451         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6452             DisplayError(_("Bad FEN received from engine"), 0);
6453             return ;
6454         } else {
6455            Reset(TRUE, FALSE);
6456            CopyBoard(boards[0], initial_position);
6457            initialRulePlies = FENrulePlies;
6458            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6459            else gameMode = MachinePlaysBlack;                 
6460            DrawPosition(FALSE, boards[currentMove]);
6461         }
6462         return;
6463     }
6464
6465     /*
6466      * Look for communication commands
6467      */
6468     if (!strncmp(message, "telluser ", 9)) {
6469         DisplayNote(message + 9);
6470         return;
6471     }
6472     if (!strncmp(message, "tellusererror ", 14)) {
6473         cps->userError = 1;
6474         DisplayError(message + 14, 0);
6475         return;
6476     }
6477     if (!strncmp(message, "tellopponent ", 13)) {
6478       if (appData.icsActive) {
6479         if (loggedOn) {
6480           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6481           SendToICS(buf1);
6482         }
6483       } else {
6484         DisplayNote(message + 13);
6485       }
6486       return;
6487     }
6488     if (!strncmp(message, "tellothers ", 11)) {
6489       if (appData.icsActive) {
6490         if (loggedOn) {
6491           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6492           SendToICS(buf1);
6493         }
6494       }
6495       return;
6496     }
6497     if (!strncmp(message, "tellall ", 8)) {
6498       if (appData.icsActive) {
6499         if (loggedOn) {
6500           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6501           SendToICS(buf1);
6502         }
6503       } else {
6504         DisplayNote(message + 8);
6505       }
6506       return;
6507     }
6508     if (strncmp(message, "warning", 7) == 0) {
6509         /* Undocumented feature, use tellusererror in new code */
6510         DisplayError(message, 0);
6511         return;
6512     }
6513     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6514         strcpy(realname, cps->tidy);
6515         strcat(realname, " query");
6516         AskQuestion(realname, buf2, buf1, cps->pr);
6517         return;
6518     }
6519     /* Commands from the engine directly to ICS.  We don't allow these to be 
6520      *  sent until we are logged on. Crafty kibitzes have been known to 
6521      *  interfere with the login process.
6522      */
6523     if (loggedOn) {
6524         if (!strncmp(message, "tellics ", 8)) {
6525             SendToICS(message + 8);
6526             SendToICS("\n");
6527             return;
6528         }
6529         if (!strncmp(message, "tellicsnoalias ", 15)) {
6530             SendToICS(ics_prefix);
6531             SendToICS(message + 15);
6532             SendToICS("\n");
6533             return;
6534         }
6535         /* The following are for backward compatibility only */
6536         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6537             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6538             SendToICS(ics_prefix);
6539             SendToICS(message);
6540             SendToICS("\n");
6541             return;
6542         }
6543     }
6544     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6545         return;
6546     }
6547     /*
6548      * If the move is illegal, cancel it and redraw the board.
6549      * Also deal with other error cases.  Matching is rather loose
6550      * here to accommodate engines written before the spec.
6551      */
6552     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6553         strncmp(message, "Error", 5) == 0) {
6554         if (StrStr(message, "name") || 
6555             StrStr(message, "rating") || StrStr(message, "?") ||
6556             StrStr(message, "result") || StrStr(message, "board") ||
6557             StrStr(message, "bk") || StrStr(message, "computer") ||
6558             StrStr(message, "variant") || StrStr(message, "hint") ||
6559             StrStr(message, "random") || StrStr(message, "depth") ||
6560             StrStr(message, "accepted")) {
6561             return;
6562         }
6563         if (StrStr(message, "protover")) {
6564           /* Program is responding to input, so it's apparently done
6565              initializing, and this error message indicates it is
6566              protocol version 1.  So we don't need to wait any longer
6567              for it to initialize and send feature commands. */
6568           FeatureDone(cps, 1);
6569           cps->protocolVersion = 1;
6570           return;
6571         }
6572         cps->maybeThinking = FALSE;
6573
6574         if (StrStr(message, "draw")) {
6575             /* Program doesn't have "draw" command */
6576             cps->sendDrawOffers = 0;
6577             return;
6578         }
6579         if (cps->sendTime != 1 &&
6580             (StrStr(message, "time") || StrStr(message, "otim"))) {
6581           /* Program apparently doesn't have "time" or "otim" command */
6582           cps->sendTime = 0;
6583           return;
6584         }
6585         if (StrStr(message, "analyze")) {
6586             cps->analysisSupport = FALSE;
6587             cps->analyzing = FALSE;
6588             Reset(FALSE, TRUE);
6589             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6590             DisplayError(buf2, 0);
6591             return;
6592         }
6593         if (StrStr(message, "(no matching move)st")) {
6594           /* Special kludge for GNU Chess 4 only */
6595           cps->stKludge = TRUE;
6596           SendTimeControl(cps, movesPerSession, timeControl,
6597                           timeIncrement, appData.searchDepth,
6598                           searchTime);
6599           return;
6600         }
6601         if (StrStr(message, "(no matching move)sd")) {
6602           /* Special kludge for GNU Chess 4 only */
6603           cps->sdKludge = TRUE;
6604           SendTimeControl(cps, movesPerSession, timeControl,
6605                           timeIncrement, appData.searchDepth,
6606                           searchTime);
6607           return;
6608         }
6609         if (!StrStr(message, "llegal")) {
6610             return;
6611         }
6612         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6613             gameMode == IcsIdle) return;
6614         if (forwardMostMove <= backwardMostMove) return;
6615         if (pausing) PauseEvent();
6616       if(appData.forceIllegal) {
6617             // [HGM] illegal: machine refused move; force position after move into it
6618           SendToProgram("force\n", cps);
6619           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6620                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6621                 // when black is to move, while there might be nothing on a2 or black
6622                 // might already have the move. So send the board as if white has the move.
6623                 // But first we must change the stm of the engine, as it refused the last move
6624                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6625                 if(WhiteOnMove(forwardMostMove)) {
6626                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6627                     SendBoard(cps, forwardMostMove); // kludgeless board
6628                 } else {
6629                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6630                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6631                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6632                 }
6633           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6634             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6635                  gameMode == TwoMachinesPlay)
6636               SendToProgram("go\n", cps);
6637             return;
6638       } else
6639         if (gameMode == PlayFromGameFile) {
6640             /* Stop reading this game file */
6641             gameMode = EditGame;
6642             ModeHighlight();
6643         }
6644         currentMove = --forwardMostMove;
6645         DisplayMove(currentMove-1); /* before DisplayMoveError */
6646         SwitchClocks();
6647         DisplayBothClocks();
6648         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6649                 parseList[currentMove], cps->which);
6650         DisplayMoveError(buf1);
6651         DrawPosition(FALSE, boards[currentMove]);
6652
6653         /* [HGM] illegal-move claim should forfeit game when Xboard */
6654         /* only passes fully legal moves                            */
6655         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6656             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6657                                 "False illegal-move claim", GE_XBOARD );
6658         }
6659         return;
6660     }
6661     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6662         /* Program has a broken "time" command that
6663            outputs a string not ending in newline.
6664            Don't use it. */
6665         cps->sendTime = 0;
6666     }
6667     
6668     /*
6669      * If chess program startup fails, exit with an error message.
6670      * Attempts to recover here are futile.
6671      */
6672     if ((StrStr(message, "unknown host") != NULL)
6673         || (StrStr(message, "No remote directory") != NULL)
6674         || (StrStr(message, "not found") != NULL)
6675         || (StrStr(message, "No such file") != NULL)
6676         || (StrStr(message, "can't alloc") != NULL)
6677         || (StrStr(message, "Permission denied") != NULL)) {
6678
6679         cps->maybeThinking = FALSE;
6680         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6681                 cps->which, cps->program, cps->host, message);
6682         RemoveInputSource(cps->isr);
6683         DisplayFatalError(buf1, 0, 1);
6684         return;
6685     }
6686     
6687     /* 
6688      * Look for hint output
6689      */
6690     if (sscanf(message, "Hint: %s", buf1) == 1) {
6691         if (cps == &first && hintRequested) {
6692             hintRequested = FALSE;
6693             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6694                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6695                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6696                                     PosFlags(forwardMostMove),
6697                                     fromY, fromX, toY, toX, promoChar, buf1);
6698                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6699                 DisplayInformation(buf2);
6700             } else {
6701                 /* Hint move could not be parsed!? */
6702               snprintf(buf2, sizeof(buf2),
6703                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6704                         buf1, cps->which);
6705                 DisplayError(buf2, 0);
6706             }
6707         } else {
6708             strcpy(lastHint, buf1);
6709         }
6710         return;
6711     }
6712
6713     /*
6714      * Ignore other messages if game is not in progress
6715      */
6716     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6717         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6718
6719     /*
6720      * look for win, lose, draw, or draw offer
6721      */
6722     if (strncmp(message, "1-0", 3) == 0) {
6723         char *p, *q, *r = "";
6724         p = strchr(message, '{');
6725         if (p) {
6726             q = strchr(p, '}');
6727             if (q) {
6728                 *q = NULLCHAR;
6729                 r = p + 1;
6730             }
6731         }
6732         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6733         return;
6734     } else if (strncmp(message, "0-1", 3) == 0) {
6735         char *p, *q, *r = "";
6736         p = strchr(message, '{');
6737         if (p) {
6738             q = strchr(p, '}');
6739             if (q) {
6740                 *q = NULLCHAR;
6741                 r = p + 1;
6742             }
6743         }
6744         /* Kludge for Arasan 4.1 bug */
6745         if (strcmp(r, "Black resigns") == 0) {
6746             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6747             return;
6748         }
6749         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6750         return;
6751     } else if (strncmp(message, "1/2", 3) == 0) {
6752         char *p, *q, *r = "";
6753         p = strchr(message, '{');
6754         if (p) {
6755             q = strchr(p, '}');
6756             if (q) {
6757                 *q = NULLCHAR;
6758                 r = p + 1;
6759             }
6760         }
6761             
6762         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6763         return;
6764
6765     } else if (strncmp(message, "White resign", 12) == 0) {
6766         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6767         return;
6768     } else if (strncmp(message, "Black resign", 12) == 0) {
6769         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6770         return;
6771     } else if (strncmp(message, "White matches", 13) == 0 ||
6772                strncmp(message, "Black matches", 13) == 0   ) {
6773         /* [HGM] ignore GNUShogi noises */
6774         return;
6775     } else if (strncmp(message, "White", 5) == 0 &&
6776                message[5] != '(' &&
6777                StrStr(message, "Black") == NULL) {
6778         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6779         return;
6780     } else if (strncmp(message, "Black", 5) == 0 &&
6781                message[5] != '(') {
6782         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6783         return;
6784     } else if (strcmp(message, "resign") == 0 ||
6785                strcmp(message, "computer resigns") == 0) {
6786         switch (gameMode) {
6787           case MachinePlaysBlack:
6788           case IcsPlayingBlack:
6789             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6790             break;
6791           case MachinePlaysWhite:
6792           case IcsPlayingWhite:
6793             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6794             break;
6795           case TwoMachinesPlay:
6796             if (cps->twoMachinesColor[0] == 'w')
6797               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6798             else
6799               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6800             break;
6801           default:
6802             /* can't happen */
6803             break;
6804         }
6805         return;
6806     } else if (strncmp(message, "opponent mates", 14) == 0) {
6807         switch (gameMode) {
6808           case MachinePlaysBlack:
6809           case IcsPlayingBlack:
6810             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6811             break;
6812           case MachinePlaysWhite:
6813           case IcsPlayingWhite:
6814             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6815             break;
6816           case TwoMachinesPlay:
6817             if (cps->twoMachinesColor[0] == 'w')
6818               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6819             else
6820               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6821             break;
6822           default:
6823             /* can't happen */
6824             break;
6825         }
6826         return;
6827     } else if (strncmp(message, "computer mates", 14) == 0) {
6828         switch (gameMode) {
6829           case MachinePlaysBlack:
6830           case IcsPlayingBlack:
6831             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6832             break;
6833           case MachinePlaysWhite:
6834           case IcsPlayingWhite:
6835             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6836             break;
6837           case TwoMachinesPlay:
6838             if (cps->twoMachinesColor[0] == 'w')
6839               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6840             else
6841               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6842             break;
6843           default:
6844             /* can't happen */
6845             break;
6846         }
6847         return;
6848     } else if (strncmp(message, "checkmate", 9) == 0) {
6849         if (WhiteOnMove(forwardMostMove)) {
6850             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6851         } else {
6852             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6853         }
6854         return;
6855     } else if (strstr(message, "Draw") != NULL ||
6856                strstr(message, "game is a draw") != NULL) {
6857         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6858         return;
6859     } else if (strstr(message, "offer") != NULL &&
6860                strstr(message, "draw") != NULL) {
6861 #if ZIPPY
6862         if (appData.zippyPlay && first.initDone) {
6863             /* Relay offer to ICS */
6864             SendToICS(ics_prefix);
6865             SendToICS("draw\n");
6866         }
6867 #endif
6868         cps->offeredDraw = 2; /* valid until this engine moves twice */
6869         if (gameMode == TwoMachinesPlay) {
6870             if (cps->other->offeredDraw) {
6871                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6872             /* [HGM] in two-machine mode we delay relaying draw offer      */
6873             /* until after we also have move, to see if it is really claim */
6874             }
6875         } else if (gameMode == MachinePlaysWhite ||
6876                    gameMode == MachinePlaysBlack) {
6877           if (userOfferedDraw) {
6878             DisplayInformation(_("Machine accepts your draw offer"));
6879             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6880           } else {
6881             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6882           }
6883         }
6884     }
6885
6886     
6887     /*
6888      * Look for thinking output
6889      */
6890     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6891           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6892                                 ) {
6893         int plylev, mvleft, mvtot, curscore, time;
6894         char mvname[MOVE_LEN];
6895         u64 nodes; // [DM]
6896         char plyext;
6897         int ignore = FALSE;
6898         int prefixHint = FALSE;
6899         mvname[0] = NULLCHAR;
6900
6901         switch (gameMode) {
6902           case MachinePlaysBlack:
6903           case IcsPlayingBlack:
6904             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6905             break;
6906           case MachinePlaysWhite:
6907           case IcsPlayingWhite:
6908             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6909             break;
6910           case AnalyzeMode:
6911           case AnalyzeFile:
6912             break;
6913           case IcsObserving: /* [DM] icsEngineAnalyze */
6914             if (!appData.icsEngineAnalyze) ignore = TRUE;
6915             break;
6916           case TwoMachinesPlay:
6917             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6918                 ignore = TRUE;
6919             }
6920             break;
6921           default:
6922             ignore = TRUE;
6923             break;
6924         }
6925
6926         if (!ignore) {
6927             buf1[0] = NULLCHAR;
6928             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6929                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6930
6931                 if (plyext != ' ' && plyext != '\t') {
6932                     time *= 100;
6933                 }
6934
6935                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6936                 if( cps->scoreIsAbsolute && 
6937                     ( gameMode == MachinePlaysBlack ||
6938                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6939                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6940                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6941                      !WhiteOnMove(currentMove)
6942                     ) )
6943                 {
6944                     curscore = -curscore;
6945                 }
6946
6947
6948                 programStats.depth = plylev;
6949                 programStats.nodes = nodes;
6950                 programStats.time = time;
6951                 programStats.score = curscore;
6952                 programStats.got_only_move = 0;
6953
6954                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6955                         int ticklen;
6956
6957                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6958                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6959                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6960                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6961                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6962                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6963                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6964                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6965                 }
6966
6967                 /* Buffer overflow protection */
6968                 if (buf1[0] != NULLCHAR) {
6969                     if (strlen(buf1) >= sizeof(programStats.movelist)
6970                         && appData.debugMode) {
6971                         fprintf(debugFP,
6972                                 "PV is too long; using the first %u bytes.\n",
6973                                 (unsigned) sizeof(programStats.movelist) - 1);
6974                     }
6975
6976                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6977                 } else {
6978                     sprintf(programStats.movelist, " no PV\n");
6979                 }
6980
6981                 if (programStats.seen_stat) {
6982                     programStats.ok_to_send = 1;
6983                 }
6984
6985                 if (strchr(programStats.movelist, '(') != NULL) {
6986                     programStats.line_is_book = 1;
6987                     programStats.nr_moves = 0;
6988                     programStats.moves_left = 0;
6989                 } else {
6990                     programStats.line_is_book = 0;
6991                 }
6992
6993                 SendProgramStatsToFrontend( cps, &programStats );
6994
6995                 /* 
6996                     [AS] Protect the thinkOutput buffer from overflow... this
6997                     is only useful if buf1 hasn't overflowed first!
6998                 */
6999                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7000                         plylev, 
7001                         (gameMode == TwoMachinesPlay ?
7002                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7003                         ((double) curscore) / 100.0,
7004                         prefixHint ? lastHint : "",
7005                         prefixHint ? " " : "" );
7006
7007                 if( buf1[0] != NULLCHAR ) {
7008                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7009
7010                     if( strlen(buf1) > max_len ) {
7011                         if( appData.debugMode) {
7012                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7013                         }
7014                         buf1[max_len+1] = '\0';
7015                     }
7016
7017                     strcat( thinkOutput, buf1 );
7018                 }
7019
7020                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7021                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7022                     DisplayMove(currentMove - 1);
7023                 }
7024                 return;
7025
7026             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7027                 /* crafty (9.25+) says "(only move) <move>"
7028                  * if there is only 1 legal move
7029                  */
7030                 sscanf(p, "(only move) %s", buf1);
7031                 sprintf(thinkOutput, "%s (only move)", buf1);
7032                 sprintf(programStats.movelist, "%s (only move)", buf1);
7033                 programStats.depth = 1;
7034                 programStats.nr_moves = 1;
7035                 programStats.moves_left = 1;
7036                 programStats.nodes = 1;
7037                 programStats.time = 1;
7038                 programStats.got_only_move = 1;
7039
7040                 /* Not really, but we also use this member to
7041                    mean "line isn't going to change" (Crafty
7042                    isn't searching, so stats won't change) */
7043                 programStats.line_is_book = 1;
7044
7045                 SendProgramStatsToFrontend( cps, &programStats );
7046                 
7047                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7048                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7049                     DisplayMove(currentMove - 1);
7050                 }
7051                 return;
7052             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7053                               &time, &nodes, &plylev, &mvleft,
7054                               &mvtot, mvname) >= 5) {
7055                 /* The stat01: line is from Crafty (9.29+) in response
7056                    to the "." command */
7057                 programStats.seen_stat = 1;
7058                 cps->maybeThinking = TRUE;
7059
7060                 if (programStats.got_only_move || !appData.periodicUpdates)
7061                   return;
7062
7063                 programStats.depth = plylev;
7064                 programStats.time = time;
7065                 programStats.nodes = nodes;
7066                 programStats.moves_left = mvleft;
7067                 programStats.nr_moves = mvtot;
7068                 strcpy(programStats.move_name, mvname);
7069                 programStats.ok_to_send = 1;
7070                 programStats.movelist[0] = '\0';
7071
7072                 SendProgramStatsToFrontend( cps, &programStats );
7073
7074                 return;
7075
7076             } else if (strncmp(message,"++",2) == 0) {
7077                 /* Crafty 9.29+ outputs this */
7078                 programStats.got_fail = 2;
7079                 return;
7080
7081             } else if (strncmp(message,"--",2) == 0) {
7082                 /* Crafty 9.29+ outputs this */
7083                 programStats.got_fail = 1;
7084                 return;
7085
7086             } else if (thinkOutput[0] != NULLCHAR &&
7087                        strncmp(message, "    ", 4) == 0) {
7088                 unsigned message_len;
7089
7090                 p = message;
7091                 while (*p && *p == ' ') p++;
7092
7093                 message_len = strlen( p );
7094
7095                 /* [AS] Avoid buffer overflow */
7096                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7097                     strcat(thinkOutput, " ");
7098                     strcat(thinkOutput, p);
7099                 }
7100
7101                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7102                     strcat(programStats.movelist, " ");
7103                     strcat(programStats.movelist, p);
7104                 }
7105
7106                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7107                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7108                     DisplayMove(currentMove - 1);
7109                 }
7110                 return;
7111             }
7112         }
7113         else {
7114             buf1[0] = NULLCHAR;
7115
7116             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7117                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7118             {
7119                 ChessProgramStats cpstats;
7120
7121                 if (plyext != ' ' && plyext != '\t') {
7122                     time *= 100;
7123                 }
7124
7125                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7126                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7127                     curscore = -curscore;
7128                 }
7129
7130                 cpstats.depth = plylev;
7131                 cpstats.nodes = nodes;
7132                 cpstats.time = time;
7133                 cpstats.score = curscore;
7134                 cpstats.got_only_move = 0;
7135                 cpstats.movelist[0] = '\0';
7136
7137                 if (buf1[0] != NULLCHAR) {
7138                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7139                 }
7140
7141                 cpstats.ok_to_send = 0;
7142                 cpstats.line_is_book = 0;
7143                 cpstats.nr_moves = 0;
7144                 cpstats.moves_left = 0;
7145
7146                 SendProgramStatsToFrontend( cps, &cpstats );
7147             }
7148         }
7149     }
7150 }
7151
7152
7153 /* Parse a game score from the character string "game", and
7154    record it as the history of the current game.  The game
7155    score is NOT assumed to start from the standard position. 
7156    The display is not updated in any way.
7157    */
7158 void
7159 ParseGameHistory(game)
7160      char *game;
7161 {
7162     ChessMove moveType;
7163     int fromX, fromY, toX, toY, boardIndex;
7164     char promoChar;
7165     char *p, *q;
7166     char buf[MSG_SIZ];
7167
7168     if (appData.debugMode)
7169       fprintf(debugFP, "Parsing game history: %s\n", game);
7170
7171     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7172     gameInfo.site = StrSave(appData.icsHost);
7173     gameInfo.date = PGNDate();
7174     gameInfo.round = StrSave("-");
7175
7176     /* Parse out names of players */
7177     while (*game == ' ') game++;
7178     p = buf;
7179     while (*game != ' ') *p++ = *game++;
7180     *p = NULLCHAR;
7181     gameInfo.white = StrSave(buf);
7182     while (*game == ' ') game++;
7183     p = buf;
7184     while (*game != ' ' && *game != '\n') *p++ = *game++;
7185     *p = NULLCHAR;
7186     gameInfo.black = StrSave(buf);
7187
7188     /* Parse moves */
7189     boardIndex = blackPlaysFirst ? 1 : 0;
7190     yynewstr(game);
7191     for (;;) {
7192         yyboardindex = boardIndex;
7193         moveType = (ChessMove) yylex();
7194         switch (moveType) {
7195           case IllegalMove:             /* maybe suicide chess, etc. */
7196   if (appData.debugMode) {
7197     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7198     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7199     setbuf(debugFP, NULL);
7200   }
7201           case WhitePromotionChancellor:
7202           case BlackPromotionChancellor:
7203           case WhitePromotionArchbishop:
7204           case BlackPromotionArchbishop:
7205           case WhitePromotionQueen:
7206           case BlackPromotionQueen:
7207           case WhitePromotionRook:
7208           case BlackPromotionRook:
7209           case WhitePromotionBishop:
7210           case BlackPromotionBishop:
7211           case WhitePromotionKnight:
7212           case BlackPromotionKnight:
7213           case WhitePromotionKing:
7214           case BlackPromotionKing:
7215           case NormalMove:
7216           case WhiteCapturesEnPassant:
7217           case BlackCapturesEnPassant:
7218           case WhiteKingSideCastle:
7219           case WhiteQueenSideCastle:
7220           case BlackKingSideCastle:
7221           case BlackQueenSideCastle:
7222           case WhiteKingSideCastleWild:
7223           case WhiteQueenSideCastleWild:
7224           case BlackKingSideCastleWild:
7225           case BlackQueenSideCastleWild:
7226           /* PUSH Fabien */
7227           case WhiteHSideCastleFR:
7228           case WhiteASideCastleFR:
7229           case BlackHSideCastleFR:
7230           case BlackASideCastleFR:
7231           /* POP Fabien */
7232             fromX = currentMoveString[0] - AAA;
7233             fromY = currentMoveString[1] - ONE;
7234             toX = currentMoveString[2] - AAA;
7235             toY = currentMoveString[3] - ONE;
7236             promoChar = currentMoveString[4];
7237             break;
7238           case WhiteDrop:
7239           case BlackDrop:
7240             fromX = moveType == WhiteDrop ?
7241               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7242             (int) CharToPiece(ToLower(currentMoveString[0]));
7243             fromY = DROP_RANK;
7244             toX = currentMoveString[2] - AAA;
7245             toY = currentMoveString[3] - ONE;
7246             promoChar = NULLCHAR;
7247             break;
7248           case AmbiguousMove:
7249             /* bug? */
7250             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7251   if (appData.debugMode) {
7252     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7253     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7254     setbuf(debugFP, NULL);
7255   }
7256             DisplayError(buf, 0);
7257             return;
7258           case ImpossibleMove:
7259             /* bug? */
7260             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7261   if (appData.debugMode) {
7262     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7263     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7264     setbuf(debugFP, NULL);
7265   }
7266             DisplayError(buf, 0);
7267             return;
7268           case (ChessMove) 0:   /* end of file */
7269             if (boardIndex < backwardMostMove) {
7270                 /* Oops, gap.  How did that happen? */
7271                 DisplayError(_("Gap in move list"), 0);
7272                 return;
7273             }
7274             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7275             if (boardIndex > forwardMostMove) {
7276                 forwardMostMove = boardIndex;
7277             }
7278             return;
7279           case ElapsedTime:
7280             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7281                 strcat(parseList[boardIndex-1], " ");
7282                 strcat(parseList[boardIndex-1], yy_text);
7283             }
7284             continue;
7285           case Comment:
7286           case PGNTag:
7287           case NAG:
7288           default:
7289             /* ignore */
7290             continue;
7291           case WhiteWins:
7292           case BlackWins:
7293           case GameIsDrawn:
7294           case GameUnfinished:
7295             if (gameMode == IcsExamining) {
7296                 if (boardIndex < backwardMostMove) {
7297                     /* Oops, gap.  How did that happen? */
7298                     return;
7299                 }
7300                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7301                 return;
7302             }
7303             gameInfo.result = moveType;
7304             p = strchr(yy_text, '{');
7305             if (p == NULL) p = strchr(yy_text, '(');
7306             if (p == NULL) {
7307                 p = yy_text;
7308                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7309             } else {
7310                 q = strchr(p, *p == '{' ? '}' : ')');
7311                 if (q != NULL) *q = NULLCHAR;
7312                 p++;
7313             }
7314             gameInfo.resultDetails = StrSave(p);
7315             continue;
7316         }
7317         if (boardIndex >= forwardMostMove &&
7318             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7319             backwardMostMove = blackPlaysFirst ? 1 : 0;
7320             return;
7321         }
7322         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7323                                  fromY, fromX, toY, toX, promoChar,
7324                                  parseList[boardIndex]);
7325         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7326         /* currentMoveString is set as a side-effect of yylex */
7327         strcpy(moveList[boardIndex], currentMoveString);
7328         strcat(moveList[boardIndex], "\n");
7329         boardIndex++;
7330         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7331         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7332           case MT_NONE:
7333           case MT_STALEMATE:
7334           default:
7335             break;
7336           case MT_CHECK:
7337             if(gameInfo.variant != VariantShogi)
7338                 strcat(parseList[boardIndex - 1], "+");
7339             break;
7340           case MT_CHECKMATE:
7341           case MT_STAINMATE:
7342             strcat(parseList[boardIndex - 1], "#");
7343             break;
7344         }
7345     }
7346 }
7347
7348
7349 /* Apply a move to the given board  */
7350 void
7351 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7352      int fromX, fromY, toX, toY;
7353      int promoChar;
7354      Board board;
7355 {
7356   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7357
7358     /* [HGM] compute & store e.p. status and castling rights for new position */
7359     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7360     { int i;
7361
7362       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7363       oldEP = (signed char)board[EP_STATUS];
7364       board[EP_STATUS] = EP_NONE;
7365
7366       if( board[toY][toX] != EmptySquare ) 
7367            board[EP_STATUS] = EP_CAPTURE;  
7368
7369       if( board[fromY][fromX] == WhitePawn ) {
7370            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7371                board[EP_STATUS] = EP_PAWN_MOVE;
7372            if( toY-fromY==2) {
7373                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7374                         gameInfo.variant != VariantBerolina || toX < fromX)
7375                       board[EP_STATUS] = toX | berolina;
7376                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7377                         gameInfo.variant != VariantBerolina || toX > fromX) 
7378                       board[EP_STATUS] = toX;
7379            }
7380       } else 
7381       if( board[fromY][fromX] == BlackPawn ) {
7382            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7383                board[EP_STATUS] = EP_PAWN_MOVE; 
7384            if( toY-fromY== -2) {
7385                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7386                         gameInfo.variant != VariantBerolina || toX < fromX)
7387                       board[EP_STATUS] = toX | berolina;
7388                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7389                         gameInfo.variant != VariantBerolina || toX > fromX) 
7390                       board[EP_STATUS] = toX;
7391            }
7392        }
7393
7394        for(i=0; i<nrCastlingRights; i++) {
7395            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7396               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7397              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7398        }
7399
7400     }
7401
7402   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7403   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7404        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7405          
7406   if (fromX == toX && fromY == toY) return;
7407
7408   if (fromY == DROP_RANK) {
7409         /* must be first */
7410         piece = board[toY][toX] = (ChessSquare) fromX;
7411   } else {
7412      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7413      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7414      if(gameInfo.variant == VariantKnightmate)
7415          king += (int) WhiteUnicorn - (int) WhiteKing;
7416
7417     /* Code added by Tord: */
7418     /* FRC castling assumed when king captures friendly rook. */
7419     if (board[fromY][fromX] == WhiteKing &&
7420              board[toY][toX] == WhiteRook) {
7421       board[fromY][fromX] = EmptySquare;
7422       board[toY][toX] = EmptySquare;
7423       if(toX > fromX) {
7424         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7425       } else {
7426         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7427       }
7428     } else if (board[fromY][fromX] == BlackKing &&
7429                board[toY][toX] == BlackRook) {
7430       board[fromY][fromX] = EmptySquare;
7431       board[toY][toX] = EmptySquare;
7432       if(toX > fromX) {
7433         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7434       } else {
7435         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7436       }
7437     /* End of code added by Tord */
7438
7439     } else if (board[fromY][fromX] == king
7440         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7441         && toY == fromY && toX > fromX+1) {
7442         board[fromY][fromX] = EmptySquare;
7443         board[toY][toX] = king;
7444         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7445         board[fromY][BOARD_RGHT-1] = EmptySquare;
7446     } else if (board[fromY][fromX] == king
7447         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7448                && toY == fromY && toX < fromX-1) {
7449         board[fromY][fromX] = EmptySquare;
7450         board[toY][toX] = king;
7451         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7452         board[fromY][BOARD_LEFT] = EmptySquare;
7453     } else if (board[fromY][fromX] == WhitePawn
7454                && toY == BOARD_HEIGHT-1
7455                && gameInfo.variant != VariantXiangqi
7456                ) {
7457         /* white pawn promotion */
7458         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7459         if (board[toY][toX] == EmptySquare) {
7460             board[toY][toX] = WhiteQueen;
7461         }
7462         if(gameInfo.variant==VariantBughouse ||
7463            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7464             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7465         board[fromY][fromX] = EmptySquare;
7466     } else if ((fromY == BOARD_HEIGHT-4)
7467                && (toX != fromX)
7468                && gameInfo.variant != VariantXiangqi
7469                && gameInfo.variant != VariantBerolina
7470                && (board[fromY][fromX] == WhitePawn)
7471                && (board[toY][toX] == EmptySquare)) {
7472         board[fromY][fromX] = EmptySquare;
7473         board[toY][toX] = WhitePawn;
7474         captured = board[toY - 1][toX];
7475         board[toY - 1][toX] = EmptySquare;
7476     } else if ((fromY == BOARD_HEIGHT-4)
7477                && (toX == fromX)
7478                && gameInfo.variant == VariantBerolina
7479                && (board[fromY][fromX] == WhitePawn)
7480                && (board[toY][toX] == EmptySquare)) {
7481         board[fromY][fromX] = EmptySquare;
7482         board[toY][toX] = WhitePawn;
7483         if(oldEP & EP_BEROLIN_A) {
7484                 captured = board[fromY][fromX-1];
7485                 board[fromY][fromX-1] = EmptySquare;
7486         }else{  captured = board[fromY][fromX+1];
7487                 board[fromY][fromX+1] = EmptySquare;
7488         }
7489     } else if (board[fromY][fromX] == king
7490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7491                && toY == fromY && toX > fromX+1) {
7492         board[fromY][fromX] = EmptySquare;
7493         board[toY][toX] = king;
7494         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7495         board[fromY][BOARD_RGHT-1] = EmptySquare;
7496     } else if (board[fromY][fromX] == king
7497         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7498                && toY == fromY && toX < fromX-1) {
7499         board[fromY][fromX] = EmptySquare;
7500         board[toY][toX] = king;
7501         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7502         board[fromY][BOARD_LEFT] = EmptySquare;
7503     } else if (fromY == 7 && fromX == 3
7504                && board[fromY][fromX] == BlackKing
7505                && toY == 7 && toX == 5) {
7506         board[fromY][fromX] = EmptySquare;
7507         board[toY][toX] = BlackKing;
7508         board[fromY][7] = EmptySquare;
7509         board[toY][4] = BlackRook;
7510     } else if (fromY == 7 && fromX == 3
7511                && board[fromY][fromX] == BlackKing
7512                && toY == 7 && toX == 1) {
7513         board[fromY][fromX] = EmptySquare;
7514         board[toY][toX] = BlackKing;
7515         board[fromY][0] = EmptySquare;
7516         board[toY][2] = BlackRook;
7517     } else if (board[fromY][fromX] == BlackPawn
7518                && toY == 0
7519                && gameInfo.variant != VariantXiangqi
7520                ) {
7521         /* black pawn promotion */
7522         board[0][toX] = CharToPiece(ToLower(promoChar));
7523         if (board[0][toX] == EmptySquare) {
7524             board[0][toX] = BlackQueen;
7525         }
7526         if(gameInfo.variant==VariantBughouse ||
7527            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7528             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7529         board[fromY][fromX] = EmptySquare;
7530     } else if ((fromY == 3)
7531                && (toX != fromX)
7532                && gameInfo.variant != VariantXiangqi
7533                && gameInfo.variant != VariantBerolina
7534                && (board[fromY][fromX] == BlackPawn)
7535                && (board[toY][toX] == EmptySquare)) {
7536         board[fromY][fromX] = EmptySquare;
7537         board[toY][toX] = BlackPawn;
7538         captured = board[toY + 1][toX];
7539         board[toY + 1][toX] = EmptySquare;
7540     } else if ((fromY == 3)
7541                && (toX == fromX)
7542                && gameInfo.variant == VariantBerolina
7543                && (board[fromY][fromX] == BlackPawn)
7544                && (board[toY][toX] == EmptySquare)) {
7545         board[fromY][fromX] = EmptySquare;
7546         board[toY][toX] = BlackPawn;
7547         if(oldEP & EP_BEROLIN_A) {
7548                 captured = board[fromY][fromX-1];
7549                 board[fromY][fromX-1] = EmptySquare;
7550         }else{  captured = board[fromY][fromX+1];
7551                 board[fromY][fromX+1] = EmptySquare;
7552         }
7553     } else {
7554         board[toY][toX] = board[fromY][fromX];
7555         board[fromY][fromX] = EmptySquare;
7556     }
7557
7558     /* [HGM] now we promote for Shogi, if needed */
7559     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7560         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7561   }
7562
7563     if (gameInfo.holdingsWidth != 0) {
7564
7565       /* !!A lot more code needs to be written to support holdings  */
7566       /* [HGM] OK, so I have written it. Holdings are stored in the */
7567       /* penultimate board files, so they are automaticlly stored   */
7568       /* in the game history.                                       */
7569       if (fromY == DROP_RANK) {
7570         /* Delete from holdings, by decreasing count */
7571         /* and erasing image if necessary            */
7572         p = (int) fromX;
7573         if(p < (int) BlackPawn) { /* white drop */
7574              p -= (int)WhitePawn;
7575                  p = PieceToNumber((ChessSquare)p);
7576              if(p >= gameInfo.holdingsSize) p = 0;
7577              if(--board[p][BOARD_WIDTH-2] <= 0)
7578                   board[p][BOARD_WIDTH-1] = EmptySquare;
7579              if((int)board[p][BOARD_WIDTH-2] < 0)
7580                         board[p][BOARD_WIDTH-2] = 0;
7581         } else {                  /* black drop */
7582              p -= (int)BlackPawn;
7583                  p = PieceToNumber((ChessSquare)p);
7584              if(p >= gameInfo.holdingsSize) p = 0;
7585              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7586                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7587              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7588                         board[BOARD_HEIGHT-1-p][1] = 0;
7589         }
7590       }
7591       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7592           && gameInfo.variant != VariantBughouse        ) {
7593         /* [HGM] holdings: Add to holdings, if holdings exist */
7594         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7595                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7596                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7597         }
7598         p = (int) captured;
7599         if (p >= (int) BlackPawn) {
7600           p -= (int)BlackPawn;
7601           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7602                   /* in Shogi restore piece to its original  first */
7603                   captured = (ChessSquare) (DEMOTED captured);
7604                   p = DEMOTED p;
7605           }
7606           p = PieceToNumber((ChessSquare)p);
7607           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7608           board[p][BOARD_WIDTH-2]++;
7609           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7610         } else {
7611           p -= (int)WhitePawn;
7612           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7613                   captured = (ChessSquare) (DEMOTED captured);
7614                   p = DEMOTED p;
7615           }
7616           p = PieceToNumber((ChessSquare)p);
7617           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7618           board[BOARD_HEIGHT-1-p][1]++;
7619           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7620         }
7621       }
7622     } else if (gameInfo.variant == VariantAtomic) {
7623       if (captured != EmptySquare) {
7624         int y, x;
7625         for (y = toY-1; y <= toY+1; y++) {
7626           for (x = toX-1; x <= toX+1; x++) {
7627             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7628                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7629               board[y][x] = EmptySquare;
7630             }
7631           }
7632         }
7633         board[toY][toX] = EmptySquare;
7634       }
7635     }
7636     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7637         /* [HGM] Shogi promotions */
7638         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7639     }
7640
7641     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7642                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7643         // [HGM] superchess: take promotion piece out of holdings
7644         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7645         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7646             if(!--board[k][BOARD_WIDTH-2])
7647                 board[k][BOARD_WIDTH-1] = EmptySquare;
7648         } else {
7649             if(!--board[BOARD_HEIGHT-1-k][1])
7650                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7651         }
7652     }
7653
7654 }
7655
7656 /* Updates forwardMostMove */
7657 void
7658 MakeMove(fromX, fromY, toX, toY, promoChar)
7659      int fromX, fromY, toX, toY;
7660      int promoChar;
7661 {
7662 //    forwardMostMove++; // [HGM] bare: moved downstream
7663
7664     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7665         int timeLeft; static int lastLoadFlag=0; int king, piece;
7666         piece = boards[forwardMostMove][fromY][fromX];
7667         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7668         if(gameInfo.variant == VariantKnightmate)
7669             king += (int) WhiteUnicorn - (int) WhiteKing;
7670         if(forwardMostMove == 0) {
7671             if(blackPlaysFirst) 
7672                 fprintf(serverMoves, "%s;", second.tidy);
7673             fprintf(serverMoves, "%s;", first.tidy);
7674             if(!blackPlaysFirst) 
7675                 fprintf(serverMoves, "%s;", second.tidy);
7676         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7677         lastLoadFlag = loadFlag;
7678         // print base move
7679         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7680         // print castling suffix
7681         if( toY == fromY && piece == king ) {
7682             if(toX-fromX > 1)
7683                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7684             if(fromX-toX >1)
7685                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7686         }
7687         // e.p. suffix
7688         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7689              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7690              boards[forwardMostMove][toY][toX] == EmptySquare
7691              && fromX != toX )
7692                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7693         // promotion suffix
7694         if(promoChar != NULLCHAR)
7695                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7696         if(!loadFlag) {
7697             fprintf(serverMoves, "/%d/%d",
7698                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7699             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7700             else                      timeLeft = blackTimeRemaining/1000;
7701             fprintf(serverMoves, "/%d", timeLeft);
7702         }
7703         fflush(serverMoves);
7704     }
7705
7706     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7707       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7708                         0, 1);
7709       return;
7710     }
7711     if (commentList[forwardMostMove+1] != NULL) {
7712         free(commentList[forwardMostMove+1]);
7713         commentList[forwardMostMove+1] = NULL;
7714     }
7715     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7716     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7717     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7718     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7719     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7720     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7721     gameInfo.result = GameUnfinished;
7722     if (gameInfo.resultDetails != NULL) {
7723         free(gameInfo.resultDetails);
7724         gameInfo.resultDetails = NULL;
7725     }
7726     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7727                               moveList[forwardMostMove - 1]);
7728     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7729                              PosFlags(forwardMostMove - 1),
7730                              fromY, fromX, toY, toX, promoChar,
7731                              parseList[forwardMostMove - 1]);
7732     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7733       case MT_NONE:
7734       case MT_STALEMATE:
7735       default:
7736         break;
7737       case MT_CHECK:
7738         if(gameInfo.variant != VariantShogi)
7739             strcat(parseList[forwardMostMove - 1], "+");
7740         break;
7741       case MT_CHECKMATE:
7742       case MT_STAINMATE:
7743         strcat(parseList[forwardMostMove - 1], "#");
7744         break;
7745     }
7746     if (appData.debugMode) {
7747         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7748     }
7749
7750 }
7751
7752 /* Updates currentMove if not pausing */
7753 void
7754 ShowMove(fromX, fromY, toX, toY)
7755 {
7756     int instant = (gameMode == PlayFromGameFile) ?
7757         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7758     if(appData.noGUI) return;
7759     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7760         if (!instant) {
7761             if (forwardMostMove == currentMove + 1) {
7762                 AnimateMove(boards[forwardMostMove - 1],
7763                             fromX, fromY, toX, toY);
7764             }
7765             if (appData.highlightLastMove) {
7766                 SetHighlights(fromX, fromY, toX, toY);
7767             }
7768         }
7769         currentMove = forwardMostMove;
7770     }
7771
7772     if (instant) return;
7773
7774     DisplayMove(currentMove - 1);
7775     DrawPosition(FALSE, boards[currentMove]);
7776     DisplayBothClocks();
7777     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7778 }
7779
7780 void SendEgtPath(ChessProgramState *cps)
7781 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7782         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7783
7784         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7785
7786         while(*p) {
7787             char c, *q = name+1, *r, *s;
7788
7789             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7790             while(*p && *p != ',') *q++ = *p++;
7791             *q++ = ':'; *q = 0;
7792             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7793                 strcmp(name, ",nalimov:") == 0 ) {
7794                 // take nalimov path from the menu-changeable option first, if it is defined
7795                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7796                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7797             } else
7798             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7799                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7800                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7801                 s = r = StrStr(s, ":") + 1; // beginning of path info
7802                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7803                 c = *r; *r = 0;             // temporarily null-terminate path info
7804                     *--q = 0;               // strip of trailig ':' from name
7805                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7806                 *r = c;
7807                 SendToProgram(buf,cps);     // send egtbpath command for this format
7808             }
7809             if(*p == ',') p++; // read away comma to position for next format name
7810         }
7811 }
7812
7813 void
7814 InitChessProgram(cps, setup)
7815      ChessProgramState *cps;
7816      int setup; /* [HGM] needed to setup FRC opening position */
7817 {
7818     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7819     if (appData.noChessProgram) return;
7820     hintRequested = FALSE;
7821     bookRequested = FALSE;
7822
7823     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7824     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7825     if(cps->memSize) { /* [HGM] memory */
7826         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7827         SendToProgram(buf, cps);
7828     }
7829     SendEgtPath(cps); /* [HGM] EGT */
7830     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7831         sprintf(buf, "cores %d\n", appData.smpCores);
7832         SendToProgram(buf, cps);
7833     }
7834
7835     SendToProgram(cps->initString, cps);
7836     if (gameInfo.variant != VariantNormal &&
7837         gameInfo.variant != VariantLoadable
7838         /* [HGM] also send variant if board size non-standard */
7839         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7840                                             ) {
7841       char *v = VariantName(gameInfo.variant);
7842       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7843         /* [HGM] in protocol 1 we have to assume all variants valid */
7844         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7845         DisplayFatalError(buf, 0, 1);
7846         return;
7847       }
7848
7849       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7850       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7851       if( gameInfo.variant == VariantXiangqi )
7852            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7853       if( gameInfo.variant == VariantShogi )
7854            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7855       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7856            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7857       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7858                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7859            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7860       if( gameInfo.variant == VariantCourier )
7861            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7862       if( gameInfo.variant == VariantSuper )
7863            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7864       if( gameInfo.variant == VariantGreat )
7865            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7866
7867       if(overruled) {
7868            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7869                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7870            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7871            if(StrStr(cps->variants, b) == NULL) { 
7872                // specific sized variant not known, check if general sizing allowed
7873                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7874                    if(StrStr(cps->variants, "boardsize") == NULL) {
7875                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7876                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7877                        DisplayFatalError(buf, 0, 1);
7878                        return;
7879                    }
7880                    /* [HGM] here we really should compare with the maximum supported board size */
7881                }
7882            }
7883       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7884       sprintf(buf, "variant %s\n", b);
7885       SendToProgram(buf, cps);
7886     }
7887     currentlyInitializedVariant = gameInfo.variant;
7888
7889     /* [HGM] send opening position in FRC to first engine */
7890     if(setup) {
7891           SendToProgram("force\n", cps);
7892           SendBoard(cps, 0);
7893           /* engine is now in force mode! Set flag to wake it up after first move. */
7894           setboardSpoiledMachineBlack = 1;
7895     }
7896
7897     if (cps->sendICS) {
7898       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7899       SendToProgram(buf, cps);
7900     }
7901     cps->maybeThinking = FALSE;
7902     cps->offeredDraw = 0;
7903     if (!appData.icsActive) {
7904         SendTimeControl(cps, movesPerSession, timeControl,
7905                         timeIncrement, appData.searchDepth,
7906                         searchTime);
7907     }
7908     if (appData.showThinking 
7909         // [HGM] thinking: four options require thinking output to be sent
7910         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7911                                 ) {
7912         SendToProgram("post\n", cps);
7913     }
7914     SendToProgram("hard\n", cps);
7915     if (!appData.ponderNextMove) {
7916         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7917            it without being sure what state we are in first.  "hard"
7918            is not a toggle, so that one is OK.
7919          */
7920         SendToProgram("easy\n", cps);
7921     }
7922     if (cps->usePing) {
7923       sprintf(buf, "ping %d\n", ++cps->lastPing);
7924       SendToProgram(buf, cps);
7925     }
7926     cps->initDone = TRUE;
7927 }   
7928
7929
7930 void
7931 StartChessProgram(cps)
7932      ChessProgramState *cps;
7933 {
7934     char buf[MSG_SIZ];
7935     int err;
7936
7937     if (appData.noChessProgram) return;
7938     cps->initDone = FALSE;
7939
7940     if (strcmp(cps->host, "localhost") == 0) {
7941         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7942     } else if (*appData.remoteShell == NULLCHAR) {
7943         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7944     } else {
7945         if (*appData.remoteUser == NULLCHAR) {
7946           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7947                     cps->program);
7948         } else {
7949           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7950                     cps->host, appData.remoteUser, cps->program);
7951         }
7952         err = StartChildProcess(buf, "", &cps->pr);
7953     }
7954     
7955     if (err != 0) {
7956         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7957         DisplayFatalError(buf, err, 1);
7958         cps->pr = NoProc;
7959         cps->isr = NULL;
7960         return;
7961     }
7962     
7963     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7964     if (cps->protocolVersion > 1) {
7965       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7966       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7967       cps->comboCnt = 0;  //                and values of combo boxes
7968       SendToProgram(buf, cps);
7969     } else {
7970       SendToProgram("xboard\n", cps);
7971     }
7972 }
7973
7974
7975 void
7976 TwoMachinesEventIfReady P((void))
7977 {
7978   if (first.lastPing != first.lastPong) {
7979     DisplayMessage("", _("Waiting for first chess program"));
7980     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7981     return;
7982   }
7983   if (second.lastPing != second.lastPong) {
7984     DisplayMessage("", _("Waiting for second chess program"));
7985     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7986     return;
7987   }
7988   ThawUI();
7989   TwoMachinesEvent();
7990 }
7991
7992 void
7993 NextMatchGame P((void))
7994 {
7995     int index; /* [HGM] autoinc: step load index during match */
7996     Reset(FALSE, TRUE);
7997     if (*appData.loadGameFile != NULLCHAR) {
7998         index = appData.loadGameIndex;
7999         if(index < 0) { // [HGM] autoinc
8000             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8001             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8002         } 
8003         LoadGameFromFile(appData.loadGameFile,
8004                          index,
8005                          appData.loadGameFile, FALSE);
8006     } else if (*appData.loadPositionFile != NULLCHAR) {
8007         index = appData.loadPositionIndex;
8008         if(index < 0) { // [HGM] autoinc
8009             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8010             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8011         } 
8012         LoadPositionFromFile(appData.loadPositionFile,
8013                              index,
8014                              appData.loadPositionFile);
8015     }
8016     TwoMachinesEventIfReady();
8017 }
8018
8019 void UserAdjudicationEvent( int result )
8020 {
8021     ChessMove gameResult = GameIsDrawn;
8022
8023     if( result > 0 ) {
8024         gameResult = WhiteWins;
8025     }
8026     else if( result < 0 ) {
8027         gameResult = BlackWins;
8028     }
8029
8030     if( gameMode == TwoMachinesPlay ) {
8031         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8032     }
8033 }
8034
8035
8036 // [HGM] save: calculate checksum of game to make games easily identifiable
8037 int StringCheckSum(char *s)
8038 {
8039         int i = 0;
8040         if(s==NULL) return 0;
8041         while(*s) i = i*259 + *s++;
8042         return i;
8043 }
8044
8045 int GameCheckSum()
8046 {
8047         int i, sum=0;
8048         for(i=backwardMostMove; i<forwardMostMove; i++) {
8049                 sum += pvInfoList[i].depth;
8050                 sum += StringCheckSum(parseList[i]);
8051                 sum += StringCheckSum(commentList[i]);
8052                 sum *= 261;
8053         }
8054         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8055         return sum + StringCheckSum(commentList[i]);
8056 } // end of save patch
8057
8058 void
8059 GameEnds(result, resultDetails, whosays)
8060      ChessMove result;
8061      char *resultDetails;
8062      int whosays;
8063 {
8064     GameMode nextGameMode;
8065     int isIcsGame;
8066     char buf[MSG_SIZ];
8067
8068     if(endingGame) return; /* [HGM] crash: forbid recursion */
8069     endingGame = 1;
8070
8071     if (appData.debugMode) {
8072       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8073               result, resultDetails ? resultDetails : "(null)", whosays);
8074     }
8075
8076     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8077         /* If we are playing on ICS, the server decides when the
8078            game is over, but the engine can offer to draw, claim 
8079            a draw, or resign. 
8080          */
8081 #if ZIPPY
8082         if (appData.zippyPlay && first.initDone) {
8083             if (result == GameIsDrawn) {
8084                 /* In case draw still needs to be claimed */
8085                 SendToICS(ics_prefix);
8086                 SendToICS("draw\n");
8087             } else if (StrCaseStr(resultDetails, "resign")) {
8088                 SendToICS(ics_prefix);
8089                 SendToICS("resign\n");
8090             }
8091         }
8092 #endif
8093         endingGame = 0; /* [HGM] crash */
8094         return;
8095     }
8096
8097     /* If we're loading the game from a file, stop */
8098     if (whosays == GE_FILE) {
8099       (void) StopLoadGameTimer();
8100       gameFileFP = NULL;
8101     }
8102
8103     /* Cancel draw offers */
8104     first.offeredDraw = second.offeredDraw = 0;
8105
8106     /* If this is an ICS game, only ICS can really say it's done;
8107        if not, anyone can. */
8108     isIcsGame = (gameMode == IcsPlayingWhite || 
8109                  gameMode == IcsPlayingBlack || 
8110                  gameMode == IcsObserving    || 
8111                  gameMode == IcsExamining);
8112
8113     if (!isIcsGame || whosays == GE_ICS) {
8114         /* OK -- not an ICS game, or ICS said it was done */
8115         StopClocks();
8116         if (!isIcsGame && !appData.noChessProgram) 
8117           SetUserThinkingEnables();
8118     
8119         /* [HGM] if a machine claims the game end we verify this claim */
8120         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8121             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8122                 char claimer;
8123                 ChessMove trueResult = (ChessMove) -1;
8124
8125                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8126                                             first.twoMachinesColor[0] :
8127                                             second.twoMachinesColor[0] ;
8128
8129                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8130                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8131                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8132                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8133                 } else
8134                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8135                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8136                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8137                 } else
8138                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8139                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8140                 }
8141
8142                 // now verify win claims, but not in drop games, as we don't understand those yet
8143                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8144                                                  || gameInfo.variant == VariantGreat) &&
8145                     (result == WhiteWins && claimer == 'w' ||
8146                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8147                       if (appData.debugMode) {
8148                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8149                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8150                       }
8151                       if(result != trueResult) {
8152                               sprintf(buf, "False win claim: '%s'", resultDetails);
8153                               result = claimer == 'w' ? BlackWins : WhiteWins;
8154                               resultDetails = buf;
8155                       }
8156                 } else
8157                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8158                     && (forwardMostMove <= backwardMostMove ||
8159                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8160                         (claimer=='b')==(forwardMostMove&1))
8161                                                                                   ) {
8162                       /* [HGM] verify: draws that were not flagged are false claims */
8163                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8164                       result = claimer == 'w' ? BlackWins : WhiteWins;
8165                       resultDetails = buf;
8166                 }
8167                 /* (Claiming a loss is accepted no questions asked!) */
8168             }
8169             /* [HGM] bare: don't allow bare King to win */
8170             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8171                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8172                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8173                && result != GameIsDrawn)
8174             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8175                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8176                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8177                         if(p >= 0 && p <= (int)WhiteKing) k++;
8178                 }
8179                 if (appData.debugMode) {
8180                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8181                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8182                 }
8183                 if(k <= 1) {
8184                         result = GameIsDrawn;
8185                         sprintf(buf, "%s but bare king", resultDetails);
8186                         resultDetails = buf;
8187                 }
8188             }
8189         }
8190
8191
8192         if(serverMoves != NULL && !loadFlag) { char c = '=';
8193             if(result==WhiteWins) c = '+';
8194             if(result==BlackWins) c = '-';
8195             if(resultDetails != NULL)
8196                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8197         }
8198         if (resultDetails != NULL) {
8199             gameInfo.result = result;
8200             gameInfo.resultDetails = StrSave(resultDetails);
8201
8202             /* display last move only if game was not loaded from file */
8203             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8204                 DisplayMove(currentMove - 1);
8205     
8206             if (forwardMostMove != 0) {
8207                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8208                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8209                                                                 ) {
8210                     if (*appData.saveGameFile != NULLCHAR) {
8211                         SaveGameToFile(appData.saveGameFile, TRUE);
8212                     } else if (appData.autoSaveGames) {
8213                         AutoSaveGame();
8214                     }
8215                     if (*appData.savePositionFile != NULLCHAR) {
8216                         SavePositionToFile(appData.savePositionFile);
8217                     }
8218                 }
8219             }
8220
8221             /* Tell program how game ended in case it is learning */
8222             /* [HGM] Moved this to after saving the PGN, just in case */
8223             /* engine died and we got here through time loss. In that */
8224             /* case we will get a fatal error writing the pipe, which */
8225             /* would otherwise lose us the PGN.                       */
8226             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8227             /* output during GameEnds should never be fatal anymore   */
8228             if (gameMode == MachinePlaysWhite ||
8229                 gameMode == MachinePlaysBlack ||
8230                 gameMode == TwoMachinesPlay ||
8231                 gameMode == IcsPlayingWhite ||
8232                 gameMode == IcsPlayingBlack ||
8233                 gameMode == BeginningOfGame) {
8234                 char buf[MSG_SIZ];
8235                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8236                         resultDetails);
8237                 if (first.pr != NoProc) {
8238                     SendToProgram(buf, &first);
8239                 }
8240                 if (second.pr != NoProc &&
8241                     gameMode == TwoMachinesPlay) {
8242                     SendToProgram(buf, &second);
8243                 }
8244             }
8245         }
8246
8247         if (appData.icsActive) {
8248             if (appData.quietPlay &&
8249                 (gameMode == IcsPlayingWhite ||
8250                  gameMode == IcsPlayingBlack)) {
8251                 SendToICS(ics_prefix);
8252                 SendToICS("set shout 1\n");
8253             }
8254             nextGameMode = IcsIdle;
8255             ics_user_moved = FALSE;
8256             /* clean up premove.  It's ugly when the game has ended and the
8257              * premove highlights are still on the board.
8258              */
8259             if (gotPremove) {
8260               gotPremove = FALSE;
8261               ClearPremoveHighlights();
8262               DrawPosition(FALSE, boards[currentMove]);
8263             }
8264             if (whosays == GE_ICS) {
8265                 switch (result) {
8266                 case WhiteWins:
8267                     if (gameMode == IcsPlayingWhite)
8268                         PlayIcsWinSound();
8269                     else if(gameMode == IcsPlayingBlack)
8270                         PlayIcsLossSound();
8271                     break;
8272                 case BlackWins:
8273                     if (gameMode == IcsPlayingBlack)
8274                         PlayIcsWinSound();
8275                     else if(gameMode == IcsPlayingWhite)
8276                         PlayIcsLossSound();
8277                     break;
8278                 case GameIsDrawn:
8279                     PlayIcsDrawSound();
8280                     break;
8281                 default:
8282                     PlayIcsUnfinishedSound();
8283                 }
8284             }
8285         } else if (gameMode == EditGame ||
8286                    gameMode == PlayFromGameFile || 
8287                    gameMode == AnalyzeMode || 
8288                    gameMode == AnalyzeFile) {
8289             nextGameMode = gameMode;
8290         } else {
8291             nextGameMode = EndOfGame;
8292         }
8293         pausing = FALSE;
8294         ModeHighlight();
8295     } else {
8296         nextGameMode = gameMode;
8297     }
8298
8299     if (appData.noChessProgram) {
8300         gameMode = nextGameMode;
8301         ModeHighlight();
8302         endingGame = 0; /* [HGM] crash */
8303         return;
8304     }
8305
8306     if (first.reuse) {
8307         /* Put first chess program into idle state */
8308         if (first.pr != NoProc &&
8309             (gameMode == MachinePlaysWhite ||
8310              gameMode == MachinePlaysBlack ||
8311              gameMode == TwoMachinesPlay ||
8312              gameMode == IcsPlayingWhite ||
8313              gameMode == IcsPlayingBlack ||
8314              gameMode == BeginningOfGame)) {
8315             SendToProgram("force\n", &first);
8316             if (first.usePing) {
8317               char buf[MSG_SIZ];
8318               sprintf(buf, "ping %d\n", ++first.lastPing);
8319               SendToProgram(buf, &first);
8320             }
8321         }
8322     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8323         /* Kill off first chess program */
8324         if (first.isr != NULL)
8325           RemoveInputSource(first.isr);
8326         first.isr = NULL;
8327     
8328         if (first.pr != NoProc) {
8329             ExitAnalyzeMode();
8330             DoSleep( appData.delayBeforeQuit );
8331             SendToProgram("quit\n", &first);
8332             DoSleep( appData.delayAfterQuit );
8333             DestroyChildProcess(first.pr, first.useSigterm);
8334         }
8335         first.pr = NoProc;
8336     }
8337     if (second.reuse) {
8338         /* Put second chess program into idle state */
8339         if (second.pr != NoProc &&
8340             gameMode == TwoMachinesPlay) {
8341             SendToProgram("force\n", &second);
8342             if (second.usePing) {
8343               char buf[MSG_SIZ];
8344               sprintf(buf, "ping %d\n", ++second.lastPing);
8345               SendToProgram(buf, &second);
8346             }
8347         }
8348     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8349         /* Kill off second chess program */
8350         if (second.isr != NULL)
8351           RemoveInputSource(second.isr);
8352         second.isr = NULL;
8353     
8354         if (second.pr != NoProc) {
8355             DoSleep( appData.delayBeforeQuit );
8356             SendToProgram("quit\n", &second);
8357             DoSleep( appData.delayAfterQuit );
8358             DestroyChildProcess(second.pr, second.useSigterm);
8359         }
8360         second.pr = NoProc;
8361     }
8362
8363     if (matchMode && gameMode == TwoMachinesPlay) {
8364         switch (result) {
8365         case WhiteWins:
8366           if (first.twoMachinesColor[0] == 'w') {
8367             first.matchWins++;
8368           } else {
8369             second.matchWins++;
8370           }
8371           break;
8372         case BlackWins:
8373           if (first.twoMachinesColor[0] == 'b') {
8374             first.matchWins++;
8375           } else {
8376             second.matchWins++;
8377           }
8378           break;
8379         default:
8380           break;
8381         }
8382         if (matchGame < appData.matchGames) {
8383             char *tmp;
8384             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8385                 tmp = first.twoMachinesColor;
8386                 first.twoMachinesColor = second.twoMachinesColor;
8387                 second.twoMachinesColor = tmp;
8388             }
8389             gameMode = nextGameMode;
8390             matchGame++;
8391             if(appData.matchPause>10000 || appData.matchPause<10)
8392                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8393             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8394             endingGame = 0; /* [HGM] crash */
8395             return;
8396         } else {
8397             char buf[MSG_SIZ];
8398             gameMode = nextGameMode;
8399             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8400                     first.tidy, second.tidy,
8401                     first.matchWins, second.matchWins,
8402                     appData.matchGames - (first.matchWins + second.matchWins));
8403             DisplayFatalError(buf, 0, 0);
8404         }
8405     }
8406     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8407         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8408       ExitAnalyzeMode();
8409     gameMode = nextGameMode;
8410     ModeHighlight();
8411     endingGame = 0;  /* [HGM] crash */
8412 }
8413
8414 /* Assumes program was just initialized (initString sent).
8415    Leaves program in force mode. */
8416 void
8417 FeedMovesToProgram(cps, upto) 
8418      ChessProgramState *cps;
8419      int upto;
8420 {
8421     int i;
8422     
8423     if (appData.debugMode)
8424       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8425               startedFromSetupPosition ? "position and " : "",
8426               backwardMostMove, upto, cps->which);
8427     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8428         // [HGM] variantswitch: make engine aware of new variant
8429         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8430                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8431         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8432         SendToProgram(buf, cps);
8433         currentlyInitializedVariant = gameInfo.variant;
8434     }
8435     SendToProgram("force\n", cps);
8436     if (startedFromSetupPosition) {
8437         SendBoard(cps, backwardMostMove);
8438     if (appData.debugMode) {
8439         fprintf(debugFP, "feedMoves\n");
8440     }
8441     }
8442     for (i = backwardMostMove; i < upto; i++) {
8443         SendMoveToProgram(i, cps);
8444     }
8445 }
8446
8447
8448 void
8449 ResurrectChessProgram()
8450 {
8451      /* The chess program may have exited.
8452         If so, restart it and feed it all the moves made so far. */
8453
8454     if (appData.noChessProgram || first.pr != NoProc) return;
8455     
8456     StartChessProgram(&first);
8457     InitChessProgram(&first, FALSE);
8458     FeedMovesToProgram(&first, currentMove);
8459
8460     if (!first.sendTime) {
8461         /* can't tell gnuchess what its clock should read,
8462            so we bow to its notion. */
8463         ResetClocks();
8464         timeRemaining[0][currentMove] = whiteTimeRemaining;
8465         timeRemaining[1][currentMove] = blackTimeRemaining;
8466     }
8467
8468     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8469                 appData.icsEngineAnalyze) && first.analysisSupport) {
8470       SendToProgram("analyze\n", &first);
8471       first.analyzing = TRUE;
8472     }
8473 }
8474
8475 /*
8476  * Button procedures
8477  */
8478 void
8479 Reset(redraw, init)
8480      int redraw, init;
8481 {
8482     int i;
8483
8484     if (appData.debugMode) {
8485         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8486                 redraw, init, gameMode);
8487     }
8488     CleanupTail(); // [HGM] vari: delete any stored variations
8489     pausing = pauseExamInvalid = FALSE;
8490     startedFromSetupPosition = blackPlaysFirst = FALSE;
8491     firstMove = TRUE;
8492     whiteFlag = blackFlag = FALSE;
8493     userOfferedDraw = FALSE;
8494     hintRequested = bookRequested = FALSE;
8495     first.maybeThinking = FALSE;
8496     second.maybeThinking = FALSE;
8497     first.bookSuspend = FALSE; // [HGM] book
8498     second.bookSuspend = FALSE;
8499     thinkOutput[0] = NULLCHAR;
8500     lastHint[0] = NULLCHAR;
8501     ClearGameInfo(&gameInfo);
8502     gameInfo.variant = StringToVariant(appData.variant);
8503     ics_user_moved = ics_clock_paused = FALSE;
8504     ics_getting_history = H_FALSE;
8505     ics_gamenum = -1;
8506     white_holding[0] = black_holding[0] = NULLCHAR;
8507     ClearProgramStats();
8508     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8509     
8510     ResetFrontEnd();
8511     ClearHighlights();
8512     flipView = appData.flipView;
8513     ClearPremoveHighlights();
8514     gotPremove = FALSE;
8515     alarmSounded = FALSE;
8516
8517     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8518     if(appData.serverMovesName != NULL) {
8519         /* [HGM] prepare to make moves file for broadcasting */
8520         clock_t t = clock();
8521         if(serverMoves != NULL) fclose(serverMoves);
8522         serverMoves = fopen(appData.serverMovesName, "r");
8523         if(serverMoves != NULL) {
8524             fclose(serverMoves);
8525             /* delay 15 sec before overwriting, so all clients can see end */
8526             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8527         }
8528         serverMoves = fopen(appData.serverMovesName, "w");
8529     }
8530
8531     ExitAnalyzeMode();
8532     gameMode = BeginningOfGame;
8533     ModeHighlight();
8534     if(appData.icsActive) gameInfo.variant = VariantNormal;
8535     currentMove = forwardMostMove = backwardMostMove = 0;
8536     InitPosition(redraw);
8537     for (i = 0; i < MAX_MOVES; i++) {
8538         if (commentList[i] != NULL) {
8539             free(commentList[i]);
8540             commentList[i] = NULL;
8541         }
8542     }
8543     ResetClocks();
8544     timeRemaining[0][0] = whiteTimeRemaining;
8545     timeRemaining[1][0] = blackTimeRemaining;
8546     if (first.pr == NULL) {
8547         StartChessProgram(&first);
8548     }
8549     if (init) {
8550             InitChessProgram(&first, startedFromSetupPosition);
8551     }
8552     DisplayTitle("");
8553     DisplayMessage("", "");
8554     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8555     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8556 }
8557
8558 void
8559 AutoPlayGameLoop()
8560 {
8561     for (;;) {
8562         if (!AutoPlayOneMove())
8563           return;
8564         if (matchMode || appData.timeDelay == 0)
8565           continue;
8566         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8567           return;
8568         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8569         break;
8570     }
8571 }
8572
8573
8574 int
8575 AutoPlayOneMove()
8576 {
8577     int fromX, fromY, toX, toY;
8578
8579     if (appData.debugMode) {
8580       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8581     }
8582
8583     if (gameMode != PlayFromGameFile)
8584       return FALSE;
8585
8586     if (currentMove >= forwardMostMove) {
8587       gameMode = EditGame;
8588       ModeHighlight();
8589
8590       /* [AS] Clear current move marker at the end of a game */
8591       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8592
8593       return FALSE;
8594     }
8595     
8596     toX = moveList[currentMove][2] - AAA;
8597     toY = moveList[currentMove][3] - ONE;
8598
8599     if (moveList[currentMove][1] == '@') {
8600         if (appData.highlightLastMove) {
8601             SetHighlights(-1, -1, toX, toY);
8602         }
8603     } else {
8604         fromX = moveList[currentMove][0] - AAA;
8605         fromY = moveList[currentMove][1] - ONE;
8606
8607         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8608
8609         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8610
8611         if (appData.highlightLastMove) {
8612             SetHighlights(fromX, fromY, toX, toY);
8613         }
8614     }
8615     DisplayMove(currentMove);
8616     SendMoveToProgram(currentMove++, &first);
8617     DisplayBothClocks();
8618     DrawPosition(FALSE, boards[currentMove]);
8619     // [HGM] PV info: always display, routine tests if empty
8620     DisplayComment(currentMove - 1, commentList[currentMove]);
8621     return TRUE;
8622 }
8623
8624
8625 int
8626 LoadGameOneMove(readAhead)
8627      ChessMove readAhead;
8628 {
8629     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8630     char promoChar = NULLCHAR;
8631     ChessMove moveType;
8632     char move[MSG_SIZ];
8633     char *p, *q;
8634     
8635     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8636         gameMode != AnalyzeMode && gameMode != Training) {
8637         gameFileFP = NULL;
8638         return FALSE;
8639     }
8640     
8641     yyboardindex = forwardMostMove;
8642     if (readAhead != (ChessMove)0) {
8643       moveType = readAhead;
8644     } else {
8645       if (gameFileFP == NULL)
8646           return FALSE;
8647       moveType = (ChessMove) yylex();
8648     }
8649     
8650     done = FALSE;
8651     switch (moveType) {
8652       case Comment:
8653         if (appData.debugMode) 
8654           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8655         p = yy_text;
8656
8657         /* append the comment but don't display it */
8658         AppendComment(currentMove, p, FALSE);
8659         return TRUE;
8660
8661       case WhiteCapturesEnPassant:
8662       case BlackCapturesEnPassant:
8663       case WhitePromotionChancellor:
8664       case BlackPromotionChancellor:
8665       case WhitePromotionArchbishop:
8666       case BlackPromotionArchbishop:
8667       case WhitePromotionCentaur:
8668       case BlackPromotionCentaur:
8669       case WhitePromotionQueen:
8670       case BlackPromotionQueen:
8671       case WhitePromotionRook:
8672       case BlackPromotionRook:
8673       case WhitePromotionBishop:
8674       case BlackPromotionBishop:
8675       case WhitePromotionKnight:
8676       case BlackPromotionKnight:
8677       case WhitePromotionKing:
8678       case BlackPromotionKing:
8679       case NormalMove:
8680       case WhiteKingSideCastle:
8681       case WhiteQueenSideCastle:
8682       case BlackKingSideCastle:
8683       case BlackQueenSideCastle:
8684       case WhiteKingSideCastleWild:
8685       case WhiteQueenSideCastleWild:
8686       case BlackKingSideCastleWild:
8687       case BlackQueenSideCastleWild:
8688       /* PUSH Fabien */
8689       case WhiteHSideCastleFR:
8690       case WhiteASideCastleFR:
8691       case BlackHSideCastleFR:
8692       case BlackASideCastleFR:
8693       /* POP Fabien */
8694         if (appData.debugMode)
8695           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8696         fromX = currentMoveString[0] - AAA;
8697         fromY = currentMoveString[1] - ONE;
8698         toX = currentMoveString[2] - AAA;
8699         toY = currentMoveString[3] - ONE;
8700         promoChar = currentMoveString[4];
8701         break;
8702
8703       case WhiteDrop:
8704       case BlackDrop:
8705         if (appData.debugMode)
8706           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8707         fromX = moveType == WhiteDrop ?
8708           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8709         (int) CharToPiece(ToLower(currentMoveString[0]));
8710         fromY = DROP_RANK;
8711         toX = currentMoveString[2] - AAA;
8712         toY = currentMoveString[3] - ONE;
8713         break;
8714
8715       case WhiteWins:
8716       case BlackWins:
8717       case GameIsDrawn:
8718       case GameUnfinished:
8719         if (appData.debugMode)
8720           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8721         p = strchr(yy_text, '{');
8722         if (p == NULL) p = strchr(yy_text, '(');
8723         if (p == NULL) {
8724             p = yy_text;
8725             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8726         } else {
8727             q = strchr(p, *p == '{' ? '}' : ')');
8728             if (q != NULL) *q = NULLCHAR;
8729             p++;
8730         }
8731         GameEnds(moveType, p, GE_FILE);
8732         done = TRUE;
8733         if (cmailMsgLoaded) {
8734             ClearHighlights();
8735             flipView = WhiteOnMove(currentMove);
8736             if (moveType == GameUnfinished) flipView = !flipView;
8737             if (appData.debugMode)
8738               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8739         }
8740         break;
8741
8742       case (ChessMove) 0:       /* end of file */
8743         if (appData.debugMode)
8744           fprintf(debugFP, "Parser hit end of file\n");
8745         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8746           case MT_NONE:
8747           case MT_CHECK:
8748             break;
8749           case MT_CHECKMATE:
8750           case MT_STAINMATE:
8751             if (WhiteOnMove(currentMove)) {
8752                 GameEnds(BlackWins, "Black mates", GE_FILE);
8753             } else {
8754                 GameEnds(WhiteWins, "White mates", GE_FILE);
8755             }
8756             break;
8757           case MT_STALEMATE:
8758             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8759             break;
8760         }
8761         done = TRUE;
8762         break;
8763
8764       case MoveNumberOne:
8765         if (lastLoadGameStart == GNUChessGame) {
8766             /* GNUChessGames have numbers, but they aren't move numbers */
8767             if (appData.debugMode)
8768               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8769                       yy_text, (int) moveType);
8770             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8771         }
8772         /* else fall thru */
8773
8774       case XBoardGame:
8775       case GNUChessGame:
8776       case PGNTag:
8777         /* Reached start of next game in file */
8778         if (appData.debugMode)
8779           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8780         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8781           case MT_NONE:
8782           case MT_CHECK:
8783             break;
8784           case MT_CHECKMATE:
8785           case MT_STAINMATE:
8786             if (WhiteOnMove(currentMove)) {
8787                 GameEnds(BlackWins, "Black mates", GE_FILE);
8788             } else {
8789                 GameEnds(WhiteWins, "White mates", GE_FILE);
8790             }
8791             break;
8792           case MT_STALEMATE:
8793             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8794             break;
8795         }
8796         done = TRUE;
8797         break;
8798
8799       case PositionDiagram:     /* should not happen; ignore */
8800       case ElapsedTime:         /* ignore */
8801       case NAG:                 /* ignore */
8802         if (appData.debugMode)
8803           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8804                   yy_text, (int) moveType);
8805         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8806
8807       case IllegalMove:
8808         if (appData.testLegality) {
8809             if (appData.debugMode)
8810               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8811             sprintf(move, _("Illegal move: %d.%s%s"),
8812                     (forwardMostMove / 2) + 1,
8813                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8814             DisplayError(move, 0);
8815             done = TRUE;
8816         } else {
8817             if (appData.debugMode)
8818               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8819                       yy_text, currentMoveString);
8820             fromX = currentMoveString[0] - AAA;
8821             fromY = currentMoveString[1] - ONE;
8822             toX = currentMoveString[2] - AAA;
8823             toY = currentMoveString[3] - ONE;
8824             promoChar = currentMoveString[4];
8825         }
8826         break;
8827
8828       case AmbiguousMove:
8829         if (appData.debugMode)
8830           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8831         sprintf(move, _("Ambiguous move: %d.%s%s"),
8832                 (forwardMostMove / 2) + 1,
8833                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8834         DisplayError(move, 0);
8835         done = TRUE;
8836         break;
8837
8838       default:
8839       case ImpossibleMove:
8840         if (appData.debugMode)
8841           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8842         sprintf(move, _("Illegal move: %d.%s%s"),
8843                 (forwardMostMove / 2) + 1,
8844                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8845         DisplayError(move, 0);
8846         done = TRUE;
8847         break;
8848     }
8849
8850     if (done) {
8851         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8852             DrawPosition(FALSE, boards[currentMove]);
8853             DisplayBothClocks();
8854             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8855               DisplayComment(currentMove - 1, commentList[currentMove]);
8856         }
8857         (void) StopLoadGameTimer();
8858         gameFileFP = NULL;
8859         cmailOldMove = forwardMostMove;
8860         return FALSE;
8861     } else {
8862         /* currentMoveString is set as a side-effect of yylex */
8863         strcat(currentMoveString, "\n");
8864         strcpy(moveList[forwardMostMove], currentMoveString);
8865         
8866         thinkOutput[0] = NULLCHAR;
8867         MakeMove(fromX, fromY, toX, toY, promoChar);
8868         currentMove = forwardMostMove;
8869         return TRUE;
8870     }
8871 }
8872
8873 /* Load the nth game from the given file */
8874 int
8875 LoadGameFromFile(filename, n, title, useList)
8876      char *filename;
8877      int n;
8878      char *title;
8879      /*Boolean*/ int useList;
8880 {
8881     FILE *f;
8882     char buf[MSG_SIZ];
8883
8884     if (strcmp(filename, "-") == 0) {
8885         f = stdin;
8886         title = "stdin";
8887     } else {
8888         f = fopen(filename, "rb");
8889         if (f == NULL) {
8890           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8891             DisplayError(buf, errno);
8892             return FALSE;
8893         }
8894     }
8895     if (fseek(f, 0, 0) == -1) {
8896         /* f is not seekable; probably a pipe */
8897         useList = FALSE;
8898     }
8899     if (useList && n == 0) {
8900         int error = GameListBuild(f);
8901         if (error) {
8902             DisplayError(_("Cannot build game list"), error);
8903         } else if (!ListEmpty(&gameList) &&
8904                    ((ListGame *) gameList.tailPred)->number > 1) {
8905             GameListPopUp(f, title);
8906             return TRUE;
8907         }
8908         GameListDestroy();
8909         n = 1;
8910     }
8911     if (n == 0) n = 1;
8912     return LoadGame(f, n, title, FALSE);
8913 }
8914
8915
8916 void
8917 MakeRegisteredMove()
8918 {
8919     int fromX, fromY, toX, toY;
8920     char promoChar;
8921     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8922         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8923           case CMAIL_MOVE:
8924           case CMAIL_DRAW:
8925             if (appData.debugMode)
8926               fprintf(debugFP, "Restoring %s for game %d\n",
8927                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8928     
8929             thinkOutput[0] = NULLCHAR;
8930             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8931             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8932             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8933             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8934             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8935             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8936             MakeMove(fromX, fromY, toX, toY, promoChar);
8937             ShowMove(fromX, fromY, toX, toY);
8938               
8939             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8940               case MT_NONE:
8941               case MT_CHECK:
8942                 break;
8943                 
8944               case MT_CHECKMATE:
8945               case MT_STAINMATE:
8946                 if (WhiteOnMove(currentMove)) {
8947                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8948                 } else {
8949                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8950                 }
8951                 break;
8952                 
8953               case MT_STALEMATE:
8954                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8955                 break;
8956             }
8957
8958             break;
8959             
8960           case CMAIL_RESIGN:
8961             if (WhiteOnMove(currentMove)) {
8962                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8963             } else {
8964                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8965             }
8966             break;
8967             
8968           case CMAIL_ACCEPT:
8969             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8970             break;
8971               
8972           default:
8973             break;
8974         }
8975     }
8976
8977     return;
8978 }
8979
8980 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8981 int
8982 CmailLoadGame(f, gameNumber, title, useList)
8983      FILE *f;
8984      int gameNumber;
8985      char *title;
8986      int useList;
8987 {
8988     int retVal;
8989
8990     if (gameNumber > nCmailGames) {
8991         DisplayError(_("No more games in this message"), 0);
8992         return FALSE;
8993     }
8994     if (f == lastLoadGameFP) {
8995         int offset = gameNumber - lastLoadGameNumber;
8996         if (offset == 0) {
8997             cmailMsg[0] = NULLCHAR;
8998             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8999                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9000                 nCmailMovesRegistered--;
9001             }
9002             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9003             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9004                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9005             }
9006         } else {
9007             if (! RegisterMove()) return FALSE;
9008         }
9009     }
9010
9011     retVal = LoadGame(f, gameNumber, title, useList);
9012
9013     /* Make move registered during previous look at this game, if any */
9014     MakeRegisteredMove();
9015
9016     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9017         commentList[currentMove]
9018           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9019         DisplayComment(currentMove - 1, commentList[currentMove]);
9020     }
9021
9022     return retVal;
9023 }
9024
9025 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9026 int
9027 ReloadGame(offset)
9028      int offset;
9029 {
9030     int gameNumber = lastLoadGameNumber + offset;
9031     if (lastLoadGameFP == NULL) {
9032         DisplayError(_("No game has been loaded yet"), 0);
9033         return FALSE;
9034     }
9035     if (gameNumber <= 0) {
9036         DisplayError(_("Can't back up any further"), 0);
9037         return FALSE;
9038     }
9039     if (cmailMsgLoaded) {
9040         return CmailLoadGame(lastLoadGameFP, gameNumber,
9041                              lastLoadGameTitle, lastLoadGameUseList);
9042     } else {
9043         return LoadGame(lastLoadGameFP, gameNumber,
9044                         lastLoadGameTitle, lastLoadGameUseList);
9045     }
9046 }
9047
9048
9049
9050 /* Load the nth game from open file f */
9051 int
9052 LoadGame(f, gameNumber, title, useList)
9053      FILE *f;
9054      int gameNumber;
9055      char *title;
9056      int useList;
9057 {
9058     ChessMove cm;
9059     char buf[MSG_SIZ];
9060     int gn = gameNumber;
9061     ListGame *lg = NULL;
9062     int numPGNTags = 0;
9063     int err;
9064     GameMode oldGameMode;
9065     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9066
9067     if (appData.debugMode) 
9068         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9069
9070     if (gameMode == Training )
9071         SetTrainingModeOff();
9072
9073     oldGameMode = gameMode;
9074     if (gameMode != BeginningOfGame) {
9075       Reset(FALSE, TRUE);
9076     }
9077
9078     gameFileFP = f;
9079     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9080         fclose(lastLoadGameFP);
9081     }
9082
9083     if (useList) {
9084         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9085         
9086         if (lg) {
9087             fseek(f, lg->offset, 0);
9088             GameListHighlight(gameNumber);
9089             gn = 1;
9090         }
9091         else {
9092             DisplayError(_("Game number out of range"), 0);
9093             return FALSE;
9094         }
9095     } else {
9096         GameListDestroy();
9097         if (fseek(f, 0, 0) == -1) {
9098             if (f == lastLoadGameFP ?
9099                 gameNumber == lastLoadGameNumber + 1 :
9100                 gameNumber == 1) {
9101                 gn = 1;
9102             } else {
9103                 DisplayError(_("Can't seek on game file"), 0);
9104                 return FALSE;
9105             }
9106         }
9107     }
9108     lastLoadGameFP = f;
9109     lastLoadGameNumber = gameNumber;
9110     strcpy(lastLoadGameTitle, title);
9111     lastLoadGameUseList = useList;
9112
9113     yynewfile(f);
9114
9115     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9116       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9117                 lg->gameInfo.black);
9118             DisplayTitle(buf);
9119     } else if (*title != NULLCHAR) {
9120         if (gameNumber > 1) {
9121             sprintf(buf, "%s %d", title, gameNumber);
9122             DisplayTitle(buf);
9123         } else {
9124             DisplayTitle(title);
9125         }
9126     }
9127
9128     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9129         gameMode = PlayFromGameFile;
9130         ModeHighlight();
9131     }
9132
9133     currentMove = forwardMostMove = backwardMostMove = 0;
9134     CopyBoard(boards[0], initialPosition);
9135     StopClocks();
9136
9137     /*
9138      * Skip the first gn-1 games in the file.
9139      * Also skip over anything that precedes an identifiable 
9140      * start of game marker, to avoid being confused by 
9141      * garbage at the start of the file.  Currently 
9142      * recognized start of game markers are the move number "1",
9143      * the pattern "gnuchess .* game", the pattern
9144      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9145      * A game that starts with one of the latter two patterns
9146      * will also have a move number 1, possibly
9147      * following a position diagram.
9148      * 5-4-02: Let's try being more lenient and allowing a game to
9149      * start with an unnumbered move.  Does that break anything?
9150      */
9151     cm = lastLoadGameStart = (ChessMove) 0;
9152     while (gn > 0) {
9153         yyboardindex = forwardMostMove;
9154         cm = (ChessMove) yylex();
9155         switch (cm) {
9156           case (ChessMove) 0:
9157             if (cmailMsgLoaded) {
9158                 nCmailGames = CMAIL_MAX_GAMES - gn;
9159             } else {
9160                 Reset(TRUE, TRUE);
9161                 DisplayError(_("Game not found in file"), 0);
9162             }
9163             return FALSE;
9164
9165           case GNUChessGame:
9166           case XBoardGame:
9167             gn--;
9168             lastLoadGameStart = cm;
9169             break;
9170             
9171           case MoveNumberOne:
9172             switch (lastLoadGameStart) {
9173               case GNUChessGame:
9174               case XBoardGame:
9175               case PGNTag:
9176                 break;
9177               case MoveNumberOne:
9178               case (ChessMove) 0:
9179                 gn--;           /* count this game */
9180                 lastLoadGameStart = cm;
9181                 break;
9182               default:
9183                 /* impossible */
9184                 break;
9185             }
9186             break;
9187
9188           case PGNTag:
9189             switch (lastLoadGameStart) {
9190               case GNUChessGame:
9191               case PGNTag:
9192               case MoveNumberOne:
9193               case (ChessMove) 0:
9194                 gn--;           /* count this game */
9195                 lastLoadGameStart = cm;
9196                 break;
9197               case XBoardGame:
9198                 lastLoadGameStart = cm; /* game counted already */
9199                 break;
9200               default:
9201                 /* impossible */
9202                 break;
9203             }
9204             if (gn > 0) {
9205                 do {
9206                     yyboardindex = forwardMostMove;
9207                     cm = (ChessMove) yylex();
9208                 } while (cm == PGNTag || cm == Comment);
9209             }
9210             break;
9211
9212           case WhiteWins:
9213           case BlackWins:
9214           case GameIsDrawn:
9215             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9216                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9217                     != CMAIL_OLD_RESULT) {
9218                     nCmailResults ++ ;
9219                     cmailResult[  CMAIL_MAX_GAMES
9220                                 - gn - 1] = CMAIL_OLD_RESULT;
9221                 }
9222             }
9223             break;
9224
9225           case NormalMove:
9226             /* Only a NormalMove can be at the start of a game
9227              * without a position diagram. */
9228             if (lastLoadGameStart == (ChessMove) 0) {
9229               gn--;
9230               lastLoadGameStart = MoveNumberOne;
9231             }
9232             break;
9233
9234           default:
9235             break;
9236         }
9237     }
9238     
9239     if (appData.debugMode)
9240       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9241
9242     if (cm == XBoardGame) {
9243         /* Skip any header junk before position diagram and/or move 1 */
9244         for (;;) {
9245             yyboardindex = forwardMostMove;
9246             cm = (ChessMove) yylex();
9247
9248             if (cm == (ChessMove) 0 ||
9249                 cm == GNUChessGame || cm == XBoardGame) {
9250                 /* Empty game; pretend end-of-file and handle later */
9251                 cm = (ChessMove) 0;
9252                 break;
9253             }
9254
9255             if (cm == MoveNumberOne || cm == PositionDiagram ||
9256                 cm == PGNTag || cm == Comment)
9257               break;
9258         }
9259     } else if (cm == GNUChessGame) {
9260         if (gameInfo.event != NULL) {
9261             free(gameInfo.event);
9262         }
9263         gameInfo.event = StrSave(yy_text);
9264     }   
9265
9266     startedFromSetupPosition = FALSE;
9267     while (cm == PGNTag) {
9268         if (appData.debugMode) 
9269           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9270         err = ParsePGNTag(yy_text, &gameInfo);
9271         if (!err) numPGNTags++;
9272
9273         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9274         if(gameInfo.variant != oldVariant) {
9275             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9276             InitPosition(TRUE);
9277             oldVariant = gameInfo.variant;
9278             if (appData.debugMode) 
9279               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9280         }
9281
9282
9283         if (gameInfo.fen != NULL) {
9284           Board initial_position;
9285           startedFromSetupPosition = TRUE;
9286           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9287             Reset(TRUE, TRUE);
9288             DisplayError(_("Bad FEN position in file"), 0);
9289             return FALSE;
9290           }
9291           CopyBoard(boards[0], initial_position);
9292           if (blackPlaysFirst) {
9293             currentMove = forwardMostMove = backwardMostMove = 1;
9294             CopyBoard(boards[1], initial_position);
9295             strcpy(moveList[0], "");
9296             strcpy(parseList[0], "");
9297             timeRemaining[0][1] = whiteTimeRemaining;
9298             timeRemaining[1][1] = blackTimeRemaining;
9299             if (commentList[0] != NULL) {
9300               commentList[1] = commentList[0];
9301               commentList[0] = NULL;
9302             }
9303           } else {
9304             currentMove = forwardMostMove = backwardMostMove = 0;
9305           }
9306           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9307           {   int i;
9308               initialRulePlies = FENrulePlies;
9309               for( i=0; i< nrCastlingRights; i++ )
9310                   initialRights[i] = initial_position[CASTLING][i];
9311           }
9312           yyboardindex = forwardMostMove;
9313           free(gameInfo.fen);
9314           gameInfo.fen = NULL;
9315         }
9316
9317         yyboardindex = forwardMostMove;
9318         cm = (ChessMove) yylex();
9319
9320         /* Handle comments interspersed among the tags */
9321         while (cm == Comment) {
9322             char *p;
9323             if (appData.debugMode) 
9324               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9325             p = yy_text;
9326             AppendComment(currentMove, p, FALSE);
9327             yyboardindex = forwardMostMove;
9328             cm = (ChessMove) yylex();
9329         }
9330     }
9331
9332     /* don't rely on existence of Event tag since if game was
9333      * pasted from clipboard the Event tag may not exist
9334      */
9335     if (numPGNTags > 0){
9336         char *tags;
9337         if (gameInfo.variant == VariantNormal) {
9338           gameInfo.variant = StringToVariant(gameInfo.event);
9339         }
9340         if (!matchMode) {
9341           if( appData.autoDisplayTags ) {
9342             tags = PGNTags(&gameInfo);
9343             TagsPopUp(tags, CmailMsg());
9344             free(tags);
9345           }
9346         }
9347     } else {
9348         /* Make something up, but don't display it now */
9349         SetGameInfo();
9350         TagsPopDown();
9351     }
9352
9353     if (cm == PositionDiagram) {
9354         int i, j;
9355         char *p;
9356         Board initial_position;
9357
9358         if (appData.debugMode)
9359           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9360
9361         if (!startedFromSetupPosition) {
9362             p = yy_text;
9363             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9364               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9365                 switch (*p) {
9366                   case '[':
9367                   case '-':
9368                   case ' ':
9369                   case '\t':
9370                   case '\n':
9371                   case '\r':
9372                     break;
9373                   default:
9374                     initial_position[i][j++] = CharToPiece(*p);
9375                     break;
9376                 }
9377             while (*p == ' ' || *p == '\t' ||
9378                    *p == '\n' || *p == '\r') p++;
9379         
9380             if (strncmp(p, "black", strlen("black"))==0)
9381               blackPlaysFirst = TRUE;
9382             else
9383               blackPlaysFirst = FALSE;
9384             startedFromSetupPosition = TRUE;
9385         
9386             CopyBoard(boards[0], initial_position);
9387             if (blackPlaysFirst) {
9388                 currentMove = forwardMostMove = backwardMostMove = 1;
9389                 CopyBoard(boards[1], initial_position);
9390                 strcpy(moveList[0], "");
9391                 strcpy(parseList[0], "");
9392                 timeRemaining[0][1] = whiteTimeRemaining;
9393                 timeRemaining[1][1] = blackTimeRemaining;
9394                 if (commentList[0] != NULL) {
9395                     commentList[1] = commentList[0];
9396                     commentList[0] = NULL;
9397                 }
9398             } else {
9399                 currentMove = forwardMostMove = backwardMostMove = 0;
9400             }
9401         }
9402         yyboardindex = forwardMostMove;
9403         cm = (ChessMove) yylex();
9404     }
9405
9406     if (first.pr == NoProc) {
9407         StartChessProgram(&first);
9408     }
9409     InitChessProgram(&first, FALSE);
9410     SendToProgram("force\n", &first);
9411     if (startedFromSetupPosition) {
9412         SendBoard(&first, forwardMostMove);
9413     if (appData.debugMode) {
9414         fprintf(debugFP, "Load Game\n");
9415     }
9416         DisplayBothClocks();
9417     }      
9418
9419     /* [HGM] server: flag to write setup moves in broadcast file as one */
9420     loadFlag = appData.suppressLoadMoves;
9421
9422     while (cm == Comment) {
9423         char *p;
9424         if (appData.debugMode) 
9425           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9426         p = yy_text;
9427         AppendComment(currentMove, p, FALSE);
9428         yyboardindex = forwardMostMove;
9429         cm = (ChessMove) yylex();
9430     }
9431
9432     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9433         cm == WhiteWins || cm == BlackWins ||
9434         cm == GameIsDrawn || cm == GameUnfinished) {
9435         DisplayMessage("", _("No moves in game"));
9436         if (cmailMsgLoaded) {
9437             if (appData.debugMode)
9438               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9439             ClearHighlights();
9440             flipView = FALSE;
9441         }
9442         DrawPosition(FALSE, boards[currentMove]);
9443         DisplayBothClocks();
9444         gameMode = EditGame;
9445         ModeHighlight();
9446         gameFileFP = NULL;
9447         cmailOldMove = 0;
9448         return TRUE;
9449     }
9450
9451     // [HGM] PV info: routine tests if comment empty
9452     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9453         DisplayComment(currentMove - 1, commentList[currentMove]);
9454     }
9455     if (!matchMode && appData.timeDelay != 0) 
9456       DrawPosition(FALSE, boards[currentMove]);
9457
9458     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9459       programStats.ok_to_send = 1;
9460     }
9461
9462     /* if the first token after the PGN tags is a move
9463      * and not move number 1, retrieve it from the parser 
9464      */
9465     if (cm != MoveNumberOne)
9466         LoadGameOneMove(cm);
9467
9468     /* load the remaining moves from the file */
9469     while (LoadGameOneMove((ChessMove)0)) {
9470       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9471       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9472     }
9473
9474     /* rewind to the start of the game */
9475     currentMove = backwardMostMove;
9476
9477     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9478
9479     if (oldGameMode == AnalyzeFile ||
9480         oldGameMode == AnalyzeMode) {
9481       AnalyzeFileEvent();
9482     }
9483
9484     if (matchMode || appData.timeDelay == 0) {
9485       ToEndEvent();
9486       gameMode = EditGame;
9487       ModeHighlight();
9488     } else if (appData.timeDelay > 0) {
9489       AutoPlayGameLoop();
9490     }
9491
9492     if (appData.debugMode) 
9493         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9494
9495     loadFlag = 0; /* [HGM] true game starts */
9496     return TRUE;
9497 }
9498
9499 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9500 int
9501 ReloadPosition(offset)
9502      int offset;
9503 {
9504     int positionNumber = lastLoadPositionNumber + offset;
9505     if (lastLoadPositionFP == NULL) {
9506         DisplayError(_("No position has been loaded yet"), 0);
9507         return FALSE;
9508     }
9509     if (positionNumber <= 0) {
9510         DisplayError(_("Can't back up any further"), 0);
9511         return FALSE;
9512     }
9513     return LoadPosition(lastLoadPositionFP, positionNumber,
9514                         lastLoadPositionTitle);
9515 }
9516
9517 /* Load the nth position from the given file */
9518 int
9519 LoadPositionFromFile(filename, n, title)
9520      char *filename;
9521      int n;
9522      char *title;
9523 {
9524     FILE *f;
9525     char buf[MSG_SIZ];
9526
9527     if (strcmp(filename, "-") == 0) {
9528         return LoadPosition(stdin, n, "stdin");
9529     } else {
9530         f = fopen(filename, "rb");
9531         if (f == NULL) {
9532             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9533             DisplayError(buf, errno);
9534             return FALSE;
9535         } else {
9536             return LoadPosition(f, n, title);
9537         }
9538     }
9539 }
9540
9541 /* Load the nth position from the given open file, and close it */
9542 int
9543 LoadPosition(f, positionNumber, title)
9544      FILE *f;
9545      int positionNumber;
9546      char *title;
9547 {
9548     char *p, line[MSG_SIZ];
9549     Board initial_position;
9550     int i, j, fenMode, pn;
9551     
9552     if (gameMode == Training )
9553         SetTrainingModeOff();
9554
9555     if (gameMode != BeginningOfGame) {
9556         Reset(FALSE, TRUE);
9557     }
9558     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9559         fclose(lastLoadPositionFP);
9560     }
9561     if (positionNumber == 0) positionNumber = 1;
9562     lastLoadPositionFP = f;
9563     lastLoadPositionNumber = positionNumber;
9564     strcpy(lastLoadPositionTitle, title);
9565     if (first.pr == NoProc) {
9566       StartChessProgram(&first);
9567       InitChessProgram(&first, FALSE);
9568     }    
9569     pn = positionNumber;
9570     if (positionNumber < 0) {
9571         /* Negative position number means to seek to that byte offset */
9572         if (fseek(f, -positionNumber, 0) == -1) {
9573             DisplayError(_("Can't seek on position file"), 0);
9574             return FALSE;
9575         };
9576         pn = 1;
9577     } else {
9578         if (fseek(f, 0, 0) == -1) {
9579             if (f == lastLoadPositionFP ?
9580                 positionNumber == lastLoadPositionNumber + 1 :
9581                 positionNumber == 1) {
9582                 pn = 1;
9583             } else {
9584                 DisplayError(_("Can't seek on position file"), 0);
9585                 return FALSE;
9586             }
9587         }
9588     }
9589     /* See if this file is FEN or old-style xboard */
9590     if (fgets(line, MSG_SIZ, f) == NULL) {
9591         DisplayError(_("Position not found in file"), 0);
9592         return FALSE;
9593     }
9594     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9595     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9596
9597     if (pn >= 2) {
9598         if (fenMode || line[0] == '#') pn--;
9599         while (pn > 0) {
9600             /* skip positions before number pn */
9601             if (fgets(line, MSG_SIZ, f) == NULL) {
9602                 Reset(TRUE, TRUE);
9603                 DisplayError(_("Position not found in file"), 0);
9604                 return FALSE;
9605             }
9606             if (fenMode || line[0] == '#') pn--;
9607         }
9608     }
9609
9610     if (fenMode) {
9611         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9612             DisplayError(_("Bad FEN position in file"), 0);
9613             return FALSE;
9614         }
9615     } else {
9616         (void) fgets(line, MSG_SIZ, f);
9617         (void) fgets(line, MSG_SIZ, f);
9618     
9619         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9620             (void) fgets(line, MSG_SIZ, f);
9621             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9622                 if (*p == ' ')
9623                   continue;
9624                 initial_position[i][j++] = CharToPiece(*p);
9625             }
9626         }
9627     
9628         blackPlaysFirst = FALSE;
9629         if (!feof(f)) {
9630             (void) fgets(line, MSG_SIZ, f);
9631             if (strncmp(line, "black", strlen("black"))==0)
9632               blackPlaysFirst = TRUE;
9633         }
9634     }
9635     startedFromSetupPosition = TRUE;
9636     
9637     SendToProgram("force\n", &first);
9638     CopyBoard(boards[0], initial_position);
9639     if (blackPlaysFirst) {
9640         currentMove = forwardMostMove = backwardMostMove = 1;
9641         strcpy(moveList[0], "");
9642         strcpy(parseList[0], "");
9643         CopyBoard(boards[1], initial_position);
9644         DisplayMessage("", _("Black to play"));
9645     } else {
9646         currentMove = forwardMostMove = backwardMostMove = 0;
9647         DisplayMessage("", _("White to play"));
9648     }
9649     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9650     SendBoard(&first, forwardMostMove);
9651     if (appData.debugMode) {
9652 int i, j;
9653   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9654   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9655         fprintf(debugFP, "Load Position\n");
9656     }
9657
9658     if (positionNumber > 1) {
9659         sprintf(line, "%s %d", title, positionNumber);
9660         DisplayTitle(line);
9661     } else {
9662         DisplayTitle(title);
9663     }
9664     gameMode = EditGame;
9665     ModeHighlight();
9666     ResetClocks();
9667     timeRemaining[0][1] = whiteTimeRemaining;
9668     timeRemaining[1][1] = blackTimeRemaining;
9669     DrawPosition(FALSE, boards[currentMove]);
9670    
9671     return TRUE;
9672 }
9673
9674
9675 void
9676 CopyPlayerNameIntoFileName(dest, src)
9677      char **dest, *src;
9678 {
9679     while (*src != NULLCHAR && *src != ',') {
9680         if (*src == ' ') {
9681             *(*dest)++ = '_';
9682             src++;
9683         } else {
9684             *(*dest)++ = *src++;
9685         }
9686     }
9687 }
9688
9689 char *DefaultFileName(ext)
9690      char *ext;
9691 {
9692     static char def[MSG_SIZ];
9693     char *p;
9694
9695     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9696         p = def;
9697         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9698         *p++ = '-';
9699         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9700         *p++ = '.';
9701         strcpy(p, ext);
9702     } else {
9703         def[0] = NULLCHAR;
9704     }
9705     return def;
9706 }
9707
9708 /* Save the current game to the given file */
9709 int
9710 SaveGameToFile(filename, append)
9711      char *filename;
9712      int append;
9713 {
9714     FILE *f;
9715     char buf[MSG_SIZ];
9716
9717     if (strcmp(filename, "-") == 0) {
9718         return SaveGame(stdout, 0, NULL);
9719     } else {
9720         f = fopen(filename, append ? "a" : "w");
9721         if (f == NULL) {
9722             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9723             DisplayError(buf, errno);
9724             return FALSE;
9725         } else {
9726             return SaveGame(f, 0, NULL);
9727         }
9728     }
9729 }
9730
9731 char *
9732 SavePart(str)
9733      char *str;
9734 {
9735     static char buf[MSG_SIZ];
9736     char *p;
9737     
9738     p = strchr(str, ' ');
9739     if (p == NULL) return str;
9740     strncpy(buf, str, p - str);
9741     buf[p - str] = NULLCHAR;
9742     return buf;
9743 }
9744
9745 #define PGN_MAX_LINE 75
9746
9747 #define PGN_SIDE_WHITE  0
9748 #define PGN_SIDE_BLACK  1
9749
9750 /* [AS] */
9751 static int FindFirstMoveOutOfBook( int side )
9752 {
9753     int result = -1;
9754
9755     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9756         int index = backwardMostMove;
9757         int has_book_hit = 0;
9758
9759         if( (index % 2) != side ) {
9760             index++;
9761         }
9762
9763         while( index < forwardMostMove ) {
9764             /* Check to see if engine is in book */
9765             int depth = pvInfoList[index].depth;
9766             int score = pvInfoList[index].score;
9767             int in_book = 0;
9768
9769             if( depth <= 2 ) {
9770                 in_book = 1;
9771             }
9772             else if( score == 0 && depth == 63 ) {
9773                 in_book = 1; /* Zappa */
9774             }
9775             else if( score == 2 && depth == 99 ) {
9776                 in_book = 1; /* Abrok */
9777             }
9778
9779             has_book_hit += in_book;
9780
9781             if( ! in_book ) {
9782                 result = index;
9783
9784                 break;
9785             }
9786
9787             index += 2;
9788         }
9789     }
9790
9791     return result;
9792 }
9793
9794 /* [AS] */
9795 void GetOutOfBookInfo( char * buf )
9796 {
9797     int oob[2];
9798     int i;
9799     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9800
9801     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9802     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9803
9804     *buf = '\0';
9805
9806     if( oob[0] >= 0 || oob[1] >= 0 ) {
9807         for( i=0; i<2; i++ ) {
9808             int idx = oob[i];
9809
9810             if( idx >= 0 ) {
9811                 if( i > 0 && oob[0] >= 0 ) {
9812                     strcat( buf, "   " );
9813                 }
9814
9815                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9816                 sprintf( buf+strlen(buf), "%s%.2f", 
9817                     pvInfoList[idx].score >= 0 ? "+" : "",
9818                     pvInfoList[idx].score / 100.0 );
9819             }
9820         }
9821     }
9822 }
9823
9824 /* Save game in PGN style and close the file */
9825 int
9826 SaveGamePGN(f)
9827      FILE *f;
9828 {
9829     int i, offset, linelen, newblock;
9830     time_t tm;
9831 //    char *movetext;
9832     char numtext[32];
9833     int movelen, numlen, blank;
9834     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9835
9836     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9837     
9838     tm = time((time_t *) NULL);
9839     
9840     PrintPGNTags(f, &gameInfo);
9841     
9842     if (backwardMostMove > 0 || startedFromSetupPosition) {
9843         char *fen = PositionToFEN(backwardMostMove, NULL);
9844         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9845         fprintf(f, "\n{--------------\n");
9846         PrintPosition(f, backwardMostMove);
9847         fprintf(f, "--------------}\n");
9848         free(fen);
9849     }
9850     else {
9851         /* [AS] Out of book annotation */
9852         if( appData.saveOutOfBookInfo ) {
9853             char buf[64];
9854
9855             GetOutOfBookInfo( buf );
9856
9857             if( buf[0] != '\0' ) {
9858                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9859             }
9860         }
9861
9862         fprintf(f, "\n");
9863     }
9864
9865     i = backwardMostMove;
9866     linelen = 0;
9867     newblock = TRUE;
9868
9869     while (i < forwardMostMove) {
9870         /* Print comments preceding this move */
9871         if (commentList[i] != NULL) {
9872             if (linelen > 0) fprintf(f, "\n");
9873             fprintf(f, "%s", commentList[i]);
9874             linelen = 0;
9875             newblock = TRUE;
9876         }
9877
9878         /* Format move number */
9879         if ((i % 2) == 0) {
9880             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9881         } else {
9882             if (newblock) {
9883                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9884             } else {
9885                 numtext[0] = NULLCHAR;
9886             }
9887         }
9888         numlen = strlen(numtext);
9889         newblock = FALSE;
9890
9891         /* Print move number */
9892         blank = linelen > 0 && numlen > 0;
9893         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9894             fprintf(f, "\n");
9895             linelen = 0;
9896             blank = 0;
9897         }
9898         if (blank) {
9899             fprintf(f, " ");
9900             linelen++;
9901         }
9902         fprintf(f, "%s", numtext);
9903         linelen += numlen;
9904
9905         /* Get move */
9906         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9907         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9908
9909         /* Print move */
9910         blank = linelen > 0 && movelen > 0;
9911         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9912             fprintf(f, "\n");
9913             linelen = 0;
9914             blank = 0;
9915         }
9916         if (blank) {
9917             fprintf(f, " ");
9918             linelen++;
9919         }
9920         fprintf(f, "%s", move_buffer);
9921         linelen += movelen;
9922
9923         /* [AS] Add PV info if present */
9924         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9925             /* [HGM] add time */
9926             char buf[MSG_SIZ]; int seconds;
9927
9928             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9929
9930             if( seconds <= 0) buf[0] = 0; else
9931             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9932                 seconds = (seconds + 4)/10; // round to full seconds
9933                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9934                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9935             }
9936
9937             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9938                 pvInfoList[i].score >= 0 ? "+" : "",
9939                 pvInfoList[i].score / 100.0,
9940                 pvInfoList[i].depth,
9941                 buf );
9942
9943             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9944
9945             /* Print score/depth */
9946             blank = linelen > 0 && movelen > 0;
9947             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9948                 fprintf(f, "\n");
9949                 linelen = 0;
9950                 blank = 0;
9951             }
9952             if (blank) {
9953                 fprintf(f, " ");
9954                 linelen++;
9955             }
9956             fprintf(f, "%s", move_buffer);
9957             linelen += movelen;
9958         }
9959
9960         i++;
9961     }
9962     
9963     /* Start a new line */
9964     if (linelen > 0) fprintf(f, "\n");
9965
9966     /* Print comments after last move */
9967     if (commentList[i] != NULL) {
9968         fprintf(f, "%s\n", commentList[i]);
9969     }
9970
9971     /* Print result */
9972     if (gameInfo.resultDetails != NULL &&
9973         gameInfo.resultDetails[0] != NULLCHAR) {
9974         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9975                 PGNResult(gameInfo.result));
9976     } else {
9977         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9978     }
9979
9980     fclose(f);
9981     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9982     return TRUE;
9983 }
9984
9985 /* Save game in old style and close the file */
9986 int
9987 SaveGameOldStyle(f)
9988      FILE *f;
9989 {
9990     int i, offset;
9991     time_t tm;
9992     
9993     tm = time((time_t *) NULL);
9994     
9995     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9996     PrintOpponents(f);
9997     
9998     if (backwardMostMove > 0 || startedFromSetupPosition) {
9999         fprintf(f, "\n[--------------\n");
10000         PrintPosition(f, backwardMostMove);
10001         fprintf(f, "--------------]\n");
10002     } else {
10003         fprintf(f, "\n");
10004     }
10005
10006     i = backwardMostMove;
10007     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10008
10009     while (i < forwardMostMove) {
10010         if (commentList[i] != NULL) {
10011             fprintf(f, "[%s]\n", commentList[i]);
10012         }
10013
10014         if ((i % 2) == 1) {
10015             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10016             i++;
10017         } else {
10018             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10019             i++;
10020             if (commentList[i] != NULL) {
10021                 fprintf(f, "\n");
10022                 continue;
10023             }
10024             if (i >= forwardMostMove) {
10025                 fprintf(f, "\n");
10026                 break;
10027             }
10028             fprintf(f, "%s\n", parseList[i]);
10029             i++;
10030         }
10031     }
10032     
10033     if (commentList[i] != NULL) {
10034         fprintf(f, "[%s]\n", commentList[i]);
10035     }
10036
10037     /* This isn't really the old style, but it's close enough */
10038     if (gameInfo.resultDetails != NULL &&
10039         gameInfo.resultDetails[0] != NULLCHAR) {
10040         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10041                 gameInfo.resultDetails);
10042     } else {
10043         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10044     }
10045
10046     fclose(f);
10047     return TRUE;
10048 }
10049
10050 /* Save the current game to open file f and close the file */
10051 int
10052 SaveGame(f, dummy, dummy2)
10053      FILE *f;
10054      int dummy;
10055      char *dummy2;
10056 {
10057     if (gameMode == EditPosition) EditPositionDone(TRUE);
10058     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10059     if (appData.oldSaveStyle)
10060       return SaveGameOldStyle(f);
10061     else
10062       return SaveGamePGN(f);
10063 }
10064
10065 /* Save the current position to the given file */
10066 int
10067 SavePositionToFile(filename)
10068      char *filename;
10069 {
10070     FILE *f;
10071     char buf[MSG_SIZ];
10072
10073     if (strcmp(filename, "-") == 0) {
10074         return SavePosition(stdout, 0, NULL);
10075     } else {
10076         f = fopen(filename, "a");
10077         if (f == NULL) {
10078             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10079             DisplayError(buf, errno);
10080             return FALSE;
10081         } else {
10082             SavePosition(f, 0, NULL);
10083             return TRUE;
10084         }
10085     }
10086 }
10087
10088 /* Save the current position to the given open file and close the file */
10089 int
10090 SavePosition(f, dummy, dummy2)
10091      FILE *f;
10092      int dummy;
10093      char *dummy2;
10094 {
10095     time_t tm;
10096     char *fen;
10097     
10098     if (gameMode == EditPosition) EditPositionDone(TRUE);
10099     if (appData.oldSaveStyle) {
10100         tm = time((time_t *) NULL);
10101     
10102         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10103         PrintOpponents(f);
10104         fprintf(f, "[--------------\n");
10105         PrintPosition(f, currentMove);
10106         fprintf(f, "--------------]\n");
10107     } else {
10108         fen = PositionToFEN(currentMove, NULL);
10109         fprintf(f, "%s\n", fen);
10110         free(fen);
10111     }
10112     fclose(f);
10113     return TRUE;
10114 }
10115
10116 void
10117 ReloadCmailMsgEvent(unregister)
10118      int unregister;
10119 {
10120 #if !WIN32
10121     static char *inFilename = NULL;
10122     static char *outFilename;
10123     int i;
10124     struct stat inbuf, outbuf;
10125     int status;
10126     
10127     /* Any registered moves are unregistered if unregister is set, */
10128     /* i.e. invoked by the signal handler */
10129     if (unregister) {
10130         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10131             cmailMoveRegistered[i] = FALSE;
10132             if (cmailCommentList[i] != NULL) {
10133                 free(cmailCommentList[i]);
10134                 cmailCommentList[i] = NULL;
10135             }
10136         }
10137         nCmailMovesRegistered = 0;
10138     }
10139
10140     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10141         cmailResult[i] = CMAIL_NOT_RESULT;
10142     }
10143     nCmailResults = 0;
10144
10145     if (inFilename == NULL) {
10146         /* Because the filenames are static they only get malloced once  */
10147         /* and they never get freed                                      */
10148         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10149         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10150
10151         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10152         sprintf(outFilename, "%s.out", appData.cmailGameName);
10153     }
10154     
10155     status = stat(outFilename, &outbuf);
10156     if (status < 0) {
10157         cmailMailedMove = FALSE;
10158     } else {
10159         status = stat(inFilename, &inbuf);
10160         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10161     }
10162     
10163     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10164        counts the games, notes how each one terminated, etc.
10165        
10166        It would be nice to remove this kludge and instead gather all
10167        the information while building the game list.  (And to keep it
10168        in the game list nodes instead of having a bunch of fixed-size
10169        parallel arrays.)  Note this will require getting each game's
10170        termination from the PGN tags, as the game list builder does
10171        not process the game moves.  --mann
10172        */
10173     cmailMsgLoaded = TRUE;
10174     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10175     
10176     /* Load first game in the file or popup game menu */
10177     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10178
10179 #endif /* !WIN32 */
10180     return;
10181 }
10182
10183 int
10184 RegisterMove()
10185 {
10186     FILE *f;
10187     char string[MSG_SIZ];
10188
10189     if (   cmailMailedMove
10190         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10191         return TRUE;            /* Allow free viewing  */
10192     }
10193
10194     /* Unregister move to ensure that we don't leave RegisterMove        */
10195     /* with the move registered when the conditions for registering no   */
10196     /* longer hold                                                       */
10197     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10198         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10199         nCmailMovesRegistered --;
10200
10201         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10202           {
10203               free(cmailCommentList[lastLoadGameNumber - 1]);
10204               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10205           }
10206     }
10207
10208     if (cmailOldMove == -1) {
10209         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10210         return FALSE;
10211     }
10212
10213     if (currentMove > cmailOldMove + 1) {
10214         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10215         return FALSE;
10216     }
10217
10218     if (currentMove < cmailOldMove) {
10219         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10220         return FALSE;
10221     }
10222
10223     if (forwardMostMove > currentMove) {
10224         /* Silently truncate extra moves */
10225         TruncateGame();
10226     }
10227
10228     if (   (currentMove == cmailOldMove + 1)
10229         || (   (currentMove == cmailOldMove)
10230             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10231                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10232         if (gameInfo.result != GameUnfinished) {
10233             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10234         }
10235
10236         if (commentList[currentMove] != NULL) {
10237             cmailCommentList[lastLoadGameNumber - 1]
10238               = StrSave(commentList[currentMove]);
10239         }
10240         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10241
10242         if (appData.debugMode)
10243           fprintf(debugFP, "Saving %s for game %d\n",
10244                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10245
10246         sprintf(string,
10247                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10248         
10249         f = fopen(string, "w");
10250         if (appData.oldSaveStyle) {
10251             SaveGameOldStyle(f); /* also closes the file */
10252             
10253             sprintf(string, "%s.pos.out", appData.cmailGameName);
10254             f = fopen(string, "w");
10255             SavePosition(f, 0, NULL); /* also closes the file */
10256         } else {
10257             fprintf(f, "{--------------\n");
10258             PrintPosition(f, currentMove);
10259             fprintf(f, "--------------}\n\n");
10260             
10261             SaveGame(f, 0, NULL); /* also closes the file*/
10262         }
10263         
10264         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10265         nCmailMovesRegistered ++;
10266     } else if (nCmailGames == 1) {
10267         DisplayError(_("You have not made a move yet"), 0);
10268         return FALSE;
10269     }
10270
10271     return TRUE;
10272 }
10273
10274 void
10275 MailMoveEvent()
10276 {
10277 #if !WIN32
10278     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10279     FILE *commandOutput;
10280     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10281     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10282     int nBuffers;
10283     int i;
10284     int archived;
10285     char *arcDir;
10286
10287     if (! cmailMsgLoaded) {
10288         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10289         return;
10290     }
10291
10292     if (nCmailGames == nCmailResults) {
10293         DisplayError(_("No unfinished games"), 0);
10294         return;
10295     }
10296
10297 #if CMAIL_PROHIBIT_REMAIL
10298     if (cmailMailedMove) {
10299         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);
10300         DisplayError(msg, 0);
10301         return;
10302     }
10303 #endif
10304
10305     if (! (cmailMailedMove || RegisterMove())) return;
10306     
10307     if (   cmailMailedMove
10308         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10309         sprintf(string, partCommandString,
10310                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10311         commandOutput = popen(string, "r");
10312
10313         if (commandOutput == NULL) {
10314             DisplayError(_("Failed to invoke cmail"), 0);
10315         } else {
10316             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10317                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10318             }
10319             if (nBuffers > 1) {
10320                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10321                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10322                 nBytes = MSG_SIZ - 1;
10323             } else {
10324                 (void) memcpy(msg, buffer, nBytes);
10325             }
10326             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10327
10328             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10329                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10330
10331                 archived = TRUE;
10332                 for (i = 0; i < nCmailGames; i ++) {
10333                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10334                         archived = FALSE;
10335                     }
10336                 }
10337                 if (   archived
10338                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10339                         != NULL)) {
10340                     sprintf(buffer, "%s/%s.%s.archive",
10341                             arcDir,
10342                             appData.cmailGameName,
10343                             gameInfo.date);
10344                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10345                     cmailMsgLoaded = FALSE;
10346                 }
10347             }
10348
10349             DisplayInformation(msg);
10350             pclose(commandOutput);
10351         }
10352     } else {
10353         if ((*cmailMsg) != '\0') {
10354             DisplayInformation(cmailMsg);
10355         }
10356     }
10357
10358     return;
10359 #endif /* !WIN32 */
10360 }
10361
10362 char *
10363 CmailMsg()
10364 {
10365 #if WIN32
10366     return NULL;
10367 #else
10368     int  prependComma = 0;
10369     char number[5];
10370     char string[MSG_SIZ];       /* Space for game-list */
10371     int  i;
10372     
10373     if (!cmailMsgLoaded) return "";
10374
10375     if (cmailMailedMove) {
10376         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10377     } else {
10378         /* Create a list of games left */
10379         sprintf(string, "[");
10380         for (i = 0; i < nCmailGames; i ++) {
10381             if (! (   cmailMoveRegistered[i]
10382                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10383                 if (prependComma) {
10384                     sprintf(number, ",%d", i + 1);
10385                 } else {
10386                     sprintf(number, "%d", i + 1);
10387                     prependComma = 1;
10388                 }
10389                 
10390                 strcat(string, number);
10391             }
10392         }
10393         strcat(string, "]");
10394
10395         if (nCmailMovesRegistered + nCmailResults == 0) {
10396             switch (nCmailGames) {
10397               case 1:
10398                 sprintf(cmailMsg,
10399                         _("Still need to make move for game\n"));
10400                 break;
10401                 
10402               case 2:
10403                 sprintf(cmailMsg,
10404                         _("Still need to make moves for both games\n"));
10405                 break;
10406                 
10407               default:
10408                 sprintf(cmailMsg,
10409                         _("Still need to make moves for all %d games\n"),
10410                         nCmailGames);
10411                 break;
10412             }
10413         } else {
10414             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10415               case 1:
10416                 sprintf(cmailMsg,
10417                         _("Still need to make a move for game %s\n"),
10418                         string);
10419                 break;
10420                 
10421               case 0:
10422                 if (nCmailResults == nCmailGames) {
10423                     sprintf(cmailMsg, _("No unfinished games\n"));
10424                 } else {
10425                     sprintf(cmailMsg, _("Ready to send mail\n"));
10426                 }
10427                 break;
10428                 
10429               default:
10430                 sprintf(cmailMsg,
10431                         _("Still need to make moves for games %s\n"),
10432                         string);
10433             }
10434         }
10435     }
10436     return cmailMsg;
10437 #endif /* WIN32 */
10438 }
10439
10440 void
10441 ResetGameEvent()
10442 {
10443     if (gameMode == Training)
10444       SetTrainingModeOff();
10445
10446     Reset(TRUE, TRUE);
10447     cmailMsgLoaded = FALSE;
10448     if (appData.icsActive) {
10449       SendToICS(ics_prefix);
10450       SendToICS("refresh\n");
10451     }
10452 }
10453
10454 void
10455 ExitEvent(status)
10456      int status;
10457 {
10458     exiting++;
10459     if (exiting > 2) {
10460       /* Give up on clean exit */
10461       exit(status);
10462     }
10463     if (exiting > 1) {
10464       /* Keep trying for clean exit */
10465       return;
10466     }
10467
10468     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10469
10470     if (telnetISR != NULL) {
10471       RemoveInputSource(telnetISR);
10472     }
10473     if (icsPR != NoProc) {
10474       DestroyChildProcess(icsPR, TRUE);
10475     }
10476
10477     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10478     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10479
10480     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10481     /* make sure this other one finishes before killing it!                  */
10482     if(endingGame) { int count = 0;
10483         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10484         while(endingGame && count++ < 10) DoSleep(1);
10485         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10486     }
10487
10488     /* Kill off chess programs */
10489     if (first.pr != NoProc) {
10490         ExitAnalyzeMode();
10491         
10492         DoSleep( appData.delayBeforeQuit );
10493         SendToProgram("quit\n", &first);
10494         DoSleep( appData.delayAfterQuit );
10495         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10496     }
10497     if (second.pr != NoProc) {
10498         DoSleep( appData.delayBeforeQuit );
10499         SendToProgram("quit\n", &second);
10500         DoSleep( appData.delayAfterQuit );
10501         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10502     }
10503     if (first.isr != NULL) {
10504         RemoveInputSource(first.isr);
10505     }
10506     if (second.isr != NULL) {
10507         RemoveInputSource(second.isr);
10508     }
10509
10510     ShutDownFrontEnd();
10511     exit(status);
10512 }
10513
10514 void
10515 PauseEvent()
10516 {
10517     if (appData.debugMode)
10518         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10519     if (pausing) {
10520         pausing = FALSE;
10521         ModeHighlight();
10522         if (gameMode == MachinePlaysWhite ||
10523             gameMode == MachinePlaysBlack) {
10524             StartClocks();
10525         } else {
10526             DisplayBothClocks();
10527         }
10528         if (gameMode == PlayFromGameFile) {
10529             if (appData.timeDelay >= 0) 
10530                 AutoPlayGameLoop();
10531         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10532             Reset(FALSE, TRUE);
10533             SendToICS(ics_prefix);
10534             SendToICS("refresh\n");
10535         } else if (currentMove < forwardMostMove) {
10536             ForwardInner(forwardMostMove);
10537         }
10538         pauseExamInvalid = FALSE;
10539     } else {
10540         switch (gameMode) {
10541           default:
10542             return;
10543           case IcsExamining:
10544             pauseExamForwardMostMove = forwardMostMove;
10545             pauseExamInvalid = FALSE;
10546             /* fall through */
10547           case IcsObserving:
10548           case IcsPlayingWhite:
10549           case IcsPlayingBlack:
10550             pausing = TRUE;
10551             ModeHighlight();
10552             return;
10553           case PlayFromGameFile:
10554             (void) StopLoadGameTimer();
10555             pausing = TRUE;
10556             ModeHighlight();
10557             break;
10558           case BeginningOfGame:
10559             if (appData.icsActive) return;
10560             /* else fall through */
10561           case MachinePlaysWhite:
10562           case MachinePlaysBlack:
10563           case TwoMachinesPlay:
10564             if (forwardMostMove == 0)
10565               return;           /* don't pause if no one has moved */
10566             if ((gameMode == MachinePlaysWhite &&
10567                  !WhiteOnMove(forwardMostMove)) ||
10568                 (gameMode == MachinePlaysBlack &&
10569                  WhiteOnMove(forwardMostMove))) {
10570                 StopClocks();
10571             }
10572             pausing = TRUE;
10573             ModeHighlight();
10574             break;
10575         }
10576     }
10577 }
10578
10579 void
10580 EditCommentEvent()
10581 {
10582     char title[MSG_SIZ];
10583
10584     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10585         strcpy(title, _("Edit comment"));
10586     } else {
10587         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10588                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10589                 parseList[currentMove - 1]);
10590     }
10591
10592     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10593 }
10594
10595
10596 void
10597 EditTagsEvent()
10598 {
10599     char *tags = PGNTags(&gameInfo);
10600     EditTagsPopUp(tags);
10601     free(tags);
10602 }
10603
10604 void
10605 AnalyzeModeEvent()
10606 {
10607     if (appData.noChessProgram || gameMode == AnalyzeMode)
10608       return;
10609
10610     if (gameMode != AnalyzeFile) {
10611         if (!appData.icsEngineAnalyze) {
10612                EditGameEvent();
10613                if (gameMode != EditGame) return;
10614         }
10615         ResurrectChessProgram();
10616         SendToProgram("analyze\n", &first);
10617         first.analyzing = TRUE;
10618         /*first.maybeThinking = TRUE;*/
10619         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10620         EngineOutputPopUp();
10621     }
10622     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10623     pausing = FALSE;
10624     ModeHighlight();
10625     SetGameInfo();
10626
10627     StartAnalysisClock();
10628     GetTimeMark(&lastNodeCountTime);
10629     lastNodeCount = 0;
10630 }
10631
10632 void
10633 AnalyzeFileEvent()
10634 {
10635     if (appData.noChessProgram || gameMode == AnalyzeFile)
10636       return;
10637
10638     if (gameMode != AnalyzeMode) {
10639         EditGameEvent();
10640         if (gameMode != EditGame) return;
10641         ResurrectChessProgram();
10642         SendToProgram("analyze\n", &first);
10643         first.analyzing = TRUE;
10644         /*first.maybeThinking = TRUE;*/
10645         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10646         EngineOutputPopUp();
10647     }
10648     gameMode = AnalyzeFile;
10649     pausing = FALSE;
10650     ModeHighlight();
10651     SetGameInfo();
10652
10653     StartAnalysisClock();
10654     GetTimeMark(&lastNodeCountTime);
10655     lastNodeCount = 0;
10656 }
10657
10658 void
10659 MachineWhiteEvent()
10660 {
10661     char buf[MSG_SIZ];
10662     char *bookHit = NULL;
10663
10664     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10665       return;
10666
10667
10668     if (gameMode == PlayFromGameFile || 
10669         gameMode == TwoMachinesPlay  || 
10670         gameMode == Training         || 
10671         gameMode == AnalyzeMode      || 
10672         gameMode == EndOfGame)
10673         EditGameEvent();
10674
10675     if (gameMode == EditPosition) 
10676         EditPositionDone(TRUE);
10677
10678     if (!WhiteOnMove(currentMove)) {
10679         DisplayError(_("It is not White's turn"), 0);
10680         return;
10681     }
10682   
10683     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10684       ExitAnalyzeMode();
10685
10686     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10687         gameMode == AnalyzeFile)
10688         TruncateGame();
10689
10690     ResurrectChessProgram();    /* in case it isn't running */
10691     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10692         gameMode = MachinePlaysWhite;
10693         ResetClocks();
10694     } else
10695     gameMode = MachinePlaysWhite;
10696     pausing = FALSE;
10697     ModeHighlight();
10698     SetGameInfo();
10699     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10700     DisplayTitle(buf);
10701     if (first.sendName) {
10702       sprintf(buf, "name %s\n", gameInfo.black);
10703       SendToProgram(buf, &first);
10704     }
10705     if (first.sendTime) {
10706       if (first.useColors) {
10707         SendToProgram("black\n", &first); /*gnu kludge*/
10708       }
10709       SendTimeRemaining(&first, TRUE);
10710     }
10711     if (first.useColors) {
10712       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10713     }
10714     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10715     SetMachineThinkingEnables();
10716     first.maybeThinking = TRUE;
10717     StartClocks();
10718     firstMove = FALSE;
10719
10720     if (appData.autoFlipView && !flipView) {
10721       flipView = !flipView;
10722       DrawPosition(FALSE, NULL);
10723       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10724     }
10725
10726     if(bookHit) { // [HGM] book: simulate book reply
10727         static char bookMove[MSG_SIZ]; // a bit generous?
10728
10729         programStats.nodes = programStats.depth = programStats.time = 
10730         programStats.score = programStats.got_only_move = 0;
10731         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10732
10733         strcpy(bookMove, "move ");
10734         strcat(bookMove, bookHit);
10735         HandleMachineMove(bookMove, &first);
10736     }
10737 }
10738
10739 void
10740 MachineBlackEvent()
10741 {
10742     char buf[MSG_SIZ];
10743    char *bookHit = NULL;
10744
10745     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10746         return;
10747
10748
10749     if (gameMode == PlayFromGameFile || 
10750         gameMode == TwoMachinesPlay  || 
10751         gameMode == Training         || 
10752         gameMode == AnalyzeMode      || 
10753         gameMode == EndOfGame)
10754         EditGameEvent();
10755
10756     if (gameMode == EditPosition) 
10757         EditPositionDone(TRUE);
10758
10759     if (WhiteOnMove(currentMove)) {
10760         DisplayError(_("It is not Black's turn"), 0);
10761         return;
10762     }
10763     
10764     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10765       ExitAnalyzeMode();
10766
10767     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10768         gameMode == AnalyzeFile)
10769         TruncateGame();
10770
10771     ResurrectChessProgram();    /* in case it isn't running */
10772     gameMode = MachinePlaysBlack;
10773     pausing = FALSE;
10774     ModeHighlight();
10775     SetGameInfo();
10776     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10777     DisplayTitle(buf);
10778     if (first.sendName) {
10779       sprintf(buf, "name %s\n", gameInfo.white);
10780       SendToProgram(buf, &first);
10781     }
10782     if (first.sendTime) {
10783       if (first.useColors) {
10784         SendToProgram("white\n", &first); /*gnu kludge*/
10785       }
10786       SendTimeRemaining(&first, FALSE);
10787     }
10788     if (first.useColors) {
10789       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10790     }
10791     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10792     SetMachineThinkingEnables();
10793     first.maybeThinking = TRUE;
10794     StartClocks();
10795
10796     if (appData.autoFlipView && flipView) {
10797       flipView = !flipView;
10798       DrawPosition(FALSE, NULL);
10799       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10800     }
10801     if(bookHit) { // [HGM] book: simulate book reply
10802         static char bookMove[MSG_SIZ]; // a bit generous?
10803
10804         programStats.nodes = programStats.depth = programStats.time = 
10805         programStats.score = programStats.got_only_move = 0;
10806         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10807
10808         strcpy(bookMove, "move ");
10809         strcat(bookMove, bookHit);
10810         HandleMachineMove(bookMove, &first);
10811     }
10812 }
10813
10814
10815 void
10816 DisplayTwoMachinesTitle()
10817 {
10818     char buf[MSG_SIZ];
10819     if (appData.matchGames > 0) {
10820         if (first.twoMachinesColor[0] == 'w') {
10821             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10822                     gameInfo.white, gameInfo.black,
10823                     first.matchWins, second.matchWins,
10824                     matchGame - 1 - (first.matchWins + second.matchWins));
10825         } else {
10826             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10827                     gameInfo.white, gameInfo.black,
10828                     second.matchWins, first.matchWins,
10829                     matchGame - 1 - (first.matchWins + second.matchWins));
10830         }
10831     } else {
10832         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10833     }
10834     DisplayTitle(buf);
10835 }
10836
10837 void
10838 TwoMachinesEvent P((void))
10839 {
10840     int i;
10841     char buf[MSG_SIZ];
10842     ChessProgramState *onmove;
10843     char *bookHit = NULL;
10844     
10845     if (appData.noChessProgram) return;
10846
10847     switch (gameMode) {
10848       case TwoMachinesPlay:
10849         return;
10850       case MachinePlaysWhite:
10851       case MachinePlaysBlack:
10852         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10853             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10854             return;
10855         }
10856         /* fall through */
10857       case BeginningOfGame:
10858       case PlayFromGameFile:
10859       case EndOfGame:
10860         EditGameEvent();
10861         if (gameMode != EditGame) return;
10862         break;
10863       case EditPosition:
10864         EditPositionDone(TRUE);
10865         break;
10866       case AnalyzeMode:
10867       case AnalyzeFile:
10868         ExitAnalyzeMode();
10869         break;
10870       case EditGame:
10871       default:
10872         break;
10873     }
10874
10875 //    forwardMostMove = currentMove;
10876     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10877     ResurrectChessProgram();    /* in case first program isn't running */
10878
10879     if (second.pr == NULL) {
10880         StartChessProgram(&second);
10881         if (second.protocolVersion == 1) {
10882           TwoMachinesEventIfReady();
10883         } else {
10884           /* kludge: allow timeout for initial "feature" command */
10885           FreezeUI();
10886           DisplayMessage("", _("Starting second chess program"));
10887           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10888         }
10889         return;
10890     }
10891     DisplayMessage("", "");
10892     InitChessProgram(&second, FALSE);
10893     SendToProgram("force\n", &second);
10894     if (startedFromSetupPosition) {
10895         SendBoard(&second, backwardMostMove);
10896     if (appData.debugMode) {
10897         fprintf(debugFP, "Two Machines\n");
10898     }
10899     }
10900     for (i = backwardMostMove; i < forwardMostMove; i++) {
10901         SendMoveToProgram(i, &second);
10902     }
10903
10904     gameMode = TwoMachinesPlay;
10905     pausing = FALSE;
10906     ModeHighlight();
10907     SetGameInfo();
10908     DisplayTwoMachinesTitle();
10909     firstMove = TRUE;
10910     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10911         onmove = &first;
10912     } else {
10913         onmove = &second;
10914     }
10915
10916     SendToProgram(first.computerString, &first);
10917     if (first.sendName) {
10918       sprintf(buf, "name %s\n", second.tidy);
10919       SendToProgram(buf, &first);
10920     }
10921     SendToProgram(second.computerString, &second);
10922     if (second.sendName) {
10923       sprintf(buf, "name %s\n", first.tidy);
10924       SendToProgram(buf, &second);
10925     }
10926
10927     ResetClocks();
10928     if (!first.sendTime || !second.sendTime) {
10929         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10930         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10931     }
10932     if (onmove->sendTime) {
10933       if (onmove->useColors) {
10934         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10935       }
10936       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10937     }
10938     if (onmove->useColors) {
10939       SendToProgram(onmove->twoMachinesColor, onmove);
10940     }
10941     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10942 //    SendToProgram("go\n", onmove);
10943     onmove->maybeThinking = TRUE;
10944     SetMachineThinkingEnables();
10945
10946     StartClocks();
10947
10948     if(bookHit) { // [HGM] book: simulate book reply
10949         static char bookMove[MSG_SIZ]; // a bit generous?
10950
10951         programStats.nodes = programStats.depth = programStats.time = 
10952         programStats.score = programStats.got_only_move = 0;
10953         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10954
10955         strcpy(bookMove, "move ");
10956         strcat(bookMove, bookHit);
10957         savedMessage = bookMove; // args for deferred call
10958         savedState = onmove;
10959         ScheduleDelayedEvent(DeferredBookMove, 1);
10960     }
10961 }
10962
10963 void
10964 TrainingEvent()
10965 {
10966     if (gameMode == Training) {
10967       SetTrainingModeOff();
10968       gameMode = PlayFromGameFile;
10969       DisplayMessage("", _("Training mode off"));
10970     } else {
10971       gameMode = Training;
10972       animateTraining = appData.animate;
10973
10974       /* make sure we are not already at the end of the game */
10975       if (currentMove < forwardMostMove) {
10976         SetTrainingModeOn();
10977         DisplayMessage("", _("Training mode on"));
10978       } else {
10979         gameMode = PlayFromGameFile;
10980         DisplayError(_("Already at end of game"), 0);
10981       }
10982     }
10983     ModeHighlight();
10984 }
10985
10986 void
10987 IcsClientEvent()
10988 {
10989     if (!appData.icsActive) return;
10990     switch (gameMode) {
10991       case IcsPlayingWhite:
10992       case IcsPlayingBlack:
10993       case IcsObserving:
10994       case IcsIdle:
10995       case BeginningOfGame:
10996       case IcsExamining:
10997         return;
10998
10999       case EditGame:
11000         break;
11001
11002       case EditPosition:
11003         EditPositionDone(TRUE);
11004         break;
11005
11006       case AnalyzeMode:
11007       case AnalyzeFile:
11008         ExitAnalyzeMode();
11009         break;
11010         
11011       default:
11012         EditGameEvent();
11013         break;
11014     }
11015
11016     gameMode = IcsIdle;
11017     ModeHighlight();
11018     return;
11019 }
11020
11021
11022 void
11023 EditGameEvent()
11024 {
11025     int i;
11026
11027     switch (gameMode) {
11028       case Training:
11029         SetTrainingModeOff();
11030         break;
11031       case MachinePlaysWhite:
11032       case MachinePlaysBlack:
11033       case BeginningOfGame:
11034         SendToProgram("force\n", &first);
11035         SetUserThinkingEnables();
11036         break;
11037       case PlayFromGameFile:
11038         (void) StopLoadGameTimer();
11039         if (gameFileFP != NULL) {
11040             gameFileFP = NULL;
11041         }
11042         break;
11043       case EditPosition:
11044         EditPositionDone(TRUE);
11045         break;
11046       case AnalyzeMode:
11047       case AnalyzeFile:
11048         ExitAnalyzeMode();
11049         SendToProgram("force\n", &first);
11050         break;
11051       case TwoMachinesPlay:
11052         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11053         ResurrectChessProgram();
11054         SetUserThinkingEnables();
11055         break;
11056       case EndOfGame:
11057         ResurrectChessProgram();
11058         break;
11059       case IcsPlayingBlack:
11060       case IcsPlayingWhite:
11061         DisplayError(_("Warning: You are still playing a game"), 0);
11062         break;
11063       case IcsObserving:
11064         DisplayError(_("Warning: You are still observing a game"), 0);
11065         break;
11066       case IcsExamining:
11067         DisplayError(_("Warning: You are still examining a game"), 0);
11068         break;
11069       case IcsIdle:
11070         break;
11071       case EditGame:
11072       default:
11073         return;
11074     }
11075     
11076     pausing = FALSE;
11077     StopClocks();
11078     first.offeredDraw = second.offeredDraw = 0;
11079
11080     if (gameMode == PlayFromGameFile) {
11081         whiteTimeRemaining = timeRemaining[0][currentMove];
11082         blackTimeRemaining = timeRemaining[1][currentMove];
11083         DisplayTitle("");
11084     }
11085
11086     if (gameMode == MachinePlaysWhite ||
11087         gameMode == MachinePlaysBlack ||
11088         gameMode == TwoMachinesPlay ||
11089         gameMode == EndOfGame) {
11090         i = forwardMostMove;
11091         while (i > currentMove) {
11092             SendToProgram("undo\n", &first);
11093             i--;
11094         }
11095         whiteTimeRemaining = timeRemaining[0][currentMove];
11096         blackTimeRemaining = timeRemaining[1][currentMove];
11097         DisplayBothClocks();
11098         if (whiteFlag || blackFlag) {
11099             whiteFlag = blackFlag = 0;
11100         }
11101         DisplayTitle("");
11102     }           
11103     
11104     gameMode = EditGame;
11105     ModeHighlight();
11106     SetGameInfo();
11107 }
11108
11109
11110 void
11111 EditPositionEvent()
11112 {
11113     if (gameMode == EditPosition) {
11114         EditGameEvent();
11115         return;
11116     }
11117     
11118     EditGameEvent();
11119     if (gameMode != EditGame) return;
11120     
11121     gameMode = EditPosition;
11122     ModeHighlight();
11123     SetGameInfo();
11124     if (currentMove > 0)
11125       CopyBoard(boards[0], boards[currentMove]);
11126     
11127     blackPlaysFirst = !WhiteOnMove(currentMove);
11128     ResetClocks();
11129     currentMove = forwardMostMove = backwardMostMove = 0;
11130     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11131     DisplayMove(-1);
11132 }
11133
11134 void
11135 ExitAnalyzeMode()
11136 {
11137     /* [DM] icsEngineAnalyze - possible call from other functions */
11138     if (appData.icsEngineAnalyze) {
11139         appData.icsEngineAnalyze = FALSE;
11140
11141         DisplayMessage("",_("Close ICS engine analyze..."));
11142     }
11143     if (first.analysisSupport && first.analyzing) {
11144       SendToProgram("exit\n", &first);
11145       first.analyzing = FALSE;
11146     }
11147     thinkOutput[0] = NULLCHAR;
11148 }
11149
11150 void
11151 EditPositionDone(Boolean fakeRights)
11152 {
11153     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11154
11155     startedFromSetupPosition = TRUE;
11156     InitChessProgram(&first, FALSE);
11157     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11158       boards[0][EP_STATUS] = EP_NONE;
11159       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11160     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11161         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11162         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11163       } else boards[0][CASTLING][2] = NoRights;
11164     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11165         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11166         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11167       } else boards[0][CASTLING][5] = NoRights;
11168     }
11169     SendToProgram("force\n", &first);
11170     if (blackPlaysFirst) {
11171         strcpy(moveList[0], "");
11172         strcpy(parseList[0], "");
11173         currentMove = forwardMostMove = backwardMostMove = 1;
11174         CopyBoard(boards[1], boards[0]);
11175     } else {
11176         currentMove = forwardMostMove = backwardMostMove = 0;
11177     }
11178     SendBoard(&first, forwardMostMove);
11179     if (appData.debugMode) {
11180         fprintf(debugFP, "EditPosDone\n");
11181     }
11182     DisplayTitle("");
11183     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11184     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11185     gameMode = EditGame;
11186     ModeHighlight();
11187     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11188     ClearHighlights(); /* [AS] */
11189 }
11190
11191 /* Pause for `ms' milliseconds */
11192 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11193 void
11194 TimeDelay(ms)
11195      long ms;
11196 {
11197     TimeMark m1, m2;
11198
11199     GetTimeMark(&m1);
11200     do {
11201         GetTimeMark(&m2);
11202     } while (SubtractTimeMarks(&m2, &m1) < ms);
11203 }
11204
11205 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11206 void
11207 SendMultiLineToICS(buf)
11208      char *buf;
11209 {
11210     char temp[MSG_SIZ+1], *p;
11211     int len;
11212
11213     len = strlen(buf);
11214     if (len > MSG_SIZ)
11215       len = MSG_SIZ;
11216   
11217     strncpy(temp, buf, len);
11218     temp[len] = 0;
11219
11220     p = temp;
11221     while (*p) {
11222         if (*p == '\n' || *p == '\r')
11223           *p = ' ';
11224         ++p;
11225     }
11226
11227     strcat(temp, "\n");
11228     SendToICS(temp);
11229     SendToPlayer(temp, strlen(temp));
11230 }
11231
11232 void
11233 SetWhiteToPlayEvent()
11234 {
11235     if (gameMode == EditPosition) {
11236         blackPlaysFirst = FALSE;
11237         DisplayBothClocks();    /* works because currentMove is 0 */
11238     } else if (gameMode == IcsExamining) {
11239         SendToICS(ics_prefix);
11240         SendToICS("tomove white\n");
11241     }
11242 }
11243
11244 void
11245 SetBlackToPlayEvent()
11246 {
11247     if (gameMode == EditPosition) {
11248         blackPlaysFirst = TRUE;
11249         currentMove = 1;        /* kludge */
11250         DisplayBothClocks();
11251         currentMove = 0;
11252     } else if (gameMode == IcsExamining) {
11253         SendToICS(ics_prefix);
11254         SendToICS("tomove black\n");
11255     }
11256 }
11257
11258 void
11259 EditPositionMenuEvent(selection, x, y)
11260      ChessSquare selection;
11261      int x, y;
11262 {
11263     char buf[MSG_SIZ];
11264     ChessSquare piece = boards[0][y][x];
11265
11266     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11267
11268     switch (selection) {
11269       case ClearBoard:
11270         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11271             SendToICS(ics_prefix);
11272             SendToICS("bsetup clear\n");
11273         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11274             SendToICS(ics_prefix);
11275             SendToICS("clearboard\n");
11276         } else {
11277             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11278                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11279                 for (y = 0; y < BOARD_HEIGHT; y++) {
11280                     if (gameMode == IcsExamining) {
11281                         if (boards[currentMove][y][x] != EmptySquare) {
11282                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11283                                     AAA + x, ONE + y);
11284                             SendToICS(buf);
11285                         }
11286                     } else {
11287                         boards[0][y][x] = p;
11288                     }
11289                 }
11290             }
11291         }
11292         if (gameMode == EditPosition) {
11293             DrawPosition(FALSE, boards[0]);
11294         }
11295         break;
11296
11297       case WhitePlay:
11298         SetWhiteToPlayEvent();
11299         break;
11300
11301       case BlackPlay:
11302         SetBlackToPlayEvent();
11303         break;
11304
11305       case EmptySquare:
11306         if (gameMode == IcsExamining) {
11307             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11308             SendToICS(buf);
11309         } else {
11310             boards[0][y][x] = EmptySquare;
11311             DrawPosition(FALSE, boards[0]);
11312         }
11313         break;
11314
11315       case PromotePiece:
11316         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11317            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11318             selection = (ChessSquare) (PROMOTED piece);
11319         } else if(piece == EmptySquare) selection = WhiteSilver;
11320         else selection = (ChessSquare)((int)piece - 1);
11321         goto defaultlabel;
11322
11323       case DemotePiece:
11324         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11325            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11326             selection = (ChessSquare) (DEMOTED piece);
11327         } else if(piece == EmptySquare) selection = BlackSilver;
11328         else selection = (ChessSquare)((int)piece + 1);       
11329         goto defaultlabel;
11330
11331       case WhiteQueen:
11332       case BlackQueen:
11333         if(gameInfo.variant == VariantShatranj ||
11334            gameInfo.variant == VariantXiangqi  ||
11335            gameInfo.variant == VariantCourier    )
11336             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11337         goto defaultlabel;
11338
11339       case WhiteKing:
11340       case BlackKing:
11341         if(gameInfo.variant == VariantXiangqi)
11342             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11343         if(gameInfo.variant == VariantKnightmate)
11344             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11345       default:
11346         defaultlabel:
11347         if (gameMode == IcsExamining) {
11348             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11349                     PieceToChar(selection), AAA + x, ONE + y);
11350             SendToICS(buf);
11351         } else {
11352             boards[0][y][x] = selection;
11353             DrawPosition(FALSE, boards[0]);
11354         }
11355         break;
11356     }
11357 }
11358
11359
11360 void
11361 DropMenuEvent(selection, x, y)
11362      ChessSquare selection;
11363      int x, y;
11364 {
11365     ChessMove moveType;
11366
11367     switch (gameMode) {
11368       case IcsPlayingWhite:
11369       case MachinePlaysBlack:
11370         if (!WhiteOnMove(currentMove)) {
11371             DisplayMoveError(_("It is Black's turn"));
11372             return;
11373         }
11374         moveType = WhiteDrop;
11375         break;
11376       case IcsPlayingBlack:
11377       case MachinePlaysWhite:
11378         if (WhiteOnMove(currentMove)) {
11379             DisplayMoveError(_("It is White's turn"));
11380             return;
11381         }
11382         moveType = BlackDrop;
11383         break;
11384       case EditGame:
11385         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11386         break;
11387       default:
11388         return;
11389     }
11390
11391     if (moveType == BlackDrop && selection < BlackPawn) {
11392       selection = (ChessSquare) ((int) selection
11393                                  + (int) BlackPawn - (int) WhitePawn);
11394     }
11395     if (boards[currentMove][y][x] != EmptySquare) {
11396         DisplayMoveError(_("That square is occupied"));
11397         return;
11398     }
11399
11400     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11401 }
11402
11403 void
11404 AcceptEvent()
11405 {
11406     /* Accept a pending offer of any kind from opponent */
11407     
11408     if (appData.icsActive) {
11409         SendToICS(ics_prefix);
11410         SendToICS("accept\n");
11411     } else if (cmailMsgLoaded) {
11412         if (currentMove == cmailOldMove &&
11413             commentList[cmailOldMove] != NULL &&
11414             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11415                    "Black offers a draw" : "White offers a draw")) {
11416             TruncateGame();
11417             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11418             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11419         } else {
11420             DisplayError(_("There is no pending offer on this move"), 0);
11421             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11422         }
11423     } else {
11424         /* Not used for offers from chess program */
11425     }
11426 }
11427
11428 void
11429 DeclineEvent()
11430 {
11431     /* Decline a pending offer of any kind from opponent */
11432     
11433     if (appData.icsActive) {
11434         SendToICS(ics_prefix);
11435         SendToICS("decline\n");
11436     } else if (cmailMsgLoaded) {
11437         if (currentMove == cmailOldMove &&
11438             commentList[cmailOldMove] != NULL &&
11439             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11440                    "Black offers a draw" : "White offers a draw")) {
11441 #ifdef NOTDEF
11442             AppendComment(cmailOldMove, "Draw declined", TRUE);
11443             DisplayComment(cmailOldMove - 1, "Draw declined");
11444 #endif /*NOTDEF*/
11445         } else {
11446             DisplayError(_("There is no pending offer on this move"), 0);
11447         }
11448     } else {
11449         /* Not used for offers from chess program */
11450     }
11451 }
11452
11453 void
11454 RematchEvent()
11455 {
11456     /* Issue ICS rematch command */
11457     if (appData.icsActive) {
11458         SendToICS(ics_prefix);
11459         SendToICS("rematch\n");
11460     }
11461 }
11462
11463 void
11464 CallFlagEvent()
11465 {
11466     /* Call your opponent's flag (claim a win on time) */
11467     if (appData.icsActive) {
11468         SendToICS(ics_prefix);
11469         SendToICS("flag\n");
11470     } else {
11471         switch (gameMode) {
11472           default:
11473             return;
11474           case MachinePlaysWhite:
11475             if (whiteFlag) {
11476                 if (blackFlag)
11477                   GameEnds(GameIsDrawn, "Both players ran out of time",
11478                            GE_PLAYER);
11479                 else
11480                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11481             } else {
11482                 DisplayError(_("Your opponent is not out of time"), 0);
11483             }
11484             break;
11485           case MachinePlaysBlack:
11486             if (blackFlag) {
11487                 if (whiteFlag)
11488                   GameEnds(GameIsDrawn, "Both players ran out of time",
11489                            GE_PLAYER);
11490                 else
11491                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11492             } else {
11493                 DisplayError(_("Your opponent is not out of time"), 0);
11494             }
11495             break;
11496         }
11497     }
11498 }
11499
11500 void
11501 DrawEvent()
11502 {
11503     /* Offer draw or accept pending draw offer from opponent */
11504     
11505     if (appData.icsActive) {
11506         /* Note: tournament rules require draw offers to be
11507            made after you make your move but before you punch
11508            your clock.  Currently ICS doesn't let you do that;
11509            instead, you immediately punch your clock after making
11510            a move, but you can offer a draw at any time. */
11511         
11512         SendToICS(ics_prefix);
11513         SendToICS("draw\n");
11514     } else if (cmailMsgLoaded) {
11515         if (currentMove == cmailOldMove &&
11516             commentList[cmailOldMove] != NULL &&
11517             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11518                    "Black offers a draw" : "White offers a draw")) {
11519             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11520             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11521         } else if (currentMove == cmailOldMove + 1) {
11522             char *offer = WhiteOnMove(cmailOldMove) ?
11523               "White offers a draw" : "Black offers a draw";
11524             AppendComment(currentMove, offer, TRUE);
11525             DisplayComment(currentMove - 1, offer);
11526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11527         } else {
11528             DisplayError(_("You must make your move before offering a draw"), 0);
11529             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11530         }
11531     } else if (first.offeredDraw) {
11532         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11533     } else {
11534         if (first.sendDrawOffers) {
11535             SendToProgram("draw\n", &first);
11536             userOfferedDraw = TRUE;
11537         }
11538     }
11539 }
11540
11541 void
11542 AdjournEvent()
11543 {
11544     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11545     
11546     if (appData.icsActive) {
11547         SendToICS(ics_prefix);
11548         SendToICS("adjourn\n");
11549     } else {
11550         /* Currently GNU Chess doesn't offer or accept Adjourns */
11551     }
11552 }
11553
11554
11555 void
11556 AbortEvent()
11557 {
11558     /* Offer Abort or accept pending Abort offer from opponent */
11559     
11560     if (appData.icsActive) {
11561         SendToICS(ics_prefix);
11562         SendToICS("abort\n");
11563     } else {
11564         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11565     }
11566 }
11567
11568 void
11569 ResignEvent()
11570 {
11571     /* Resign.  You can do this even if it's not your turn. */
11572     
11573     if (appData.icsActive) {
11574         SendToICS(ics_prefix);
11575         SendToICS("resign\n");
11576     } else {
11577         switch (gameMode) {
11578           case MachinePlaysWhite:
11579             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11580             break;
11581           case MachinePlaysBlack:
11582             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11583             break;
11584           case EditGame:
11585             if (cmailMsgLoaded) {
11586                 TruncateGame();
11587                 if (WhiteOnMove(cmailOldMove)) {
11588                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11589                 } else {
11590                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11591                 }
11592                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11593             }
11594             break;
11595           default:
11596             break;
11597         }
11598     }
11599 }
11600
11601
11602 void
11603 StopObservingEvent()
11604 {
11605     /* Stop observing current games */
11606     SendToICS(ics_prefix);
11607     SendToICS("unobserve\n");
11608 }
11609
11610 void
11611 StopExaminingEvent()
11612 {
11613     /* Stop observing current game */
11614     SendToICS(ics_prefix);
11615     SendToICS("unexamine\n");
11616 }
11617
11618 void
11619 ForwardInner(target)
11620      int target;
11621 {
11622     int limit;
11623
11624     if (appData.debugMode)
11625         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11626                 target, currentMove, forwardMostMove);
11627
11628     if (gameMode == EditPosition)
11629       return;
11630
11631     if (gameMode == PlayFromGameFile && !pausing)
11632       PauseEvent();
11633     
11634     if (gameMode == IcsExamining && pausing)
11635       limit = pauseExamForwardMostMove;
11636     else
11637       limit = forwardMostMove;
11638     
11639     if (target > limit) target = limit;
11640
11641     if (target > 0 && moveList[target - 1][0]) {
11642         int fromX, fromY, toX, toY;
11643         toX = moveList[target - 1][2] - AAA;
11644         toY = moveList[target - 1][3] - ONE;
11645         if (moveList[target - 1][1] == '@') {
11646             if (appData.highlightLastMove) {
11647                 SetHighlights(-1, -1, toX, toY);
11648             }
11649         } else {
11650             fromX = moveList[target - 1][0] - AAA;
11651             fromY = moveList[target - 1][1] - ONE;
11652             if (target == currentMove + 1) {
11653                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11654             }
11655             if (appData.highlightLastMove) {
11656                 SetHighlights(fromX, fromY, toX, toY);
11657             }
11658         }
11659     }
11660     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11661         gameMode == Training || gameMode == PlayFromGameFile || 
11662         gameMode == AnalyzeFile) {
11663         while (currentMove < target) {
11664             SendMoveToProgram(currentMove++, &first);
11665         }
11666     } else {
11667         currentMove = target;
11668     }
11669     
11670     if (gameMode == EditGame || gameMode == EndOfGame) {
11671         whiteTimeRemaining = timeRemaining[0][currentMove];
11672         blackTimeRemaining = timeRemaining[1][currentMove];
11673     }
11674     DisplayBothClocks();
11675     DisplayMove(currentMove - 1);
11676     DrawPosition(FALSE, boards[currentMove]);
11677     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11678     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11679         DisplayComment(currentMove - 1, commentList[currentMove]);
11680     }
11681 }
11682
11683
11684 void
11685 ForwardEvent()
11686 {
11687     if (gameMode == IcsExamining && !pausing) {
11688         SendToICS(ics_prefix);
11689         SendToICS("forward\n");
11690     } else {
11691         ForwardInner(currentMove + 1);
11692     }
11693 }
11694
11695 void
11696 ToEndEvent()
11697 {
11698     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11699         /* to optimze, we temporarily turn off analysis mode while we feed
11700          * the remaining moves to the engine. Otherwise we get analysis output
11701          * after each move.
11702          */ 
11703         if (first.analysisSupport) {
11704           SendToProgram("exit\nforce\n", &first);
11705           first.analyzing = FALSE;
11706         }
11707     }
11708         
11709     if (gameMode == IcsExamining && !pausing) {
11710         SendToICS(ics_prefix);
11711         SendToICS("forward 999999\n");
11712     } else {
11713         ForwardInner(forwardMostMove);
11714     }
11715
11716     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11717         /* we have fed all the moves, so reactivate analysis mode */
11718         SendToProgram("analyze\n", &first);
11719         first.analyzing = TRUE;
11720         /*first.maybeThinking = TRUE;*/
11721         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11722     }
11723 }
11724
11725 void
11726 BackwardInner(target)
11727      int target;
11728 {
11729     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11730
11731     if (appData.debugMode)
11732         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11733                 target, currentMove, forwardMostMove);
11734
11735     if (gameMode == EditPosition) return;
11736     if (currentMove <= backwardMostMove) {
11737         ClearHighlights();
11738         DrawPosition(full_redraw, boards[currentMove]);
11739         return;
11740     }
11741     if (gameMode == PlayFromGameFile && !pausing)
11742       PauseEvent();
11743     
11744     if (moveList[target][0]) {
11745         int fromX, fromY, toX, toY;
11746         toX = moveList[target][2] - AAA;
11747         toY = moveList[target][3] - ONE;
11748         if (moveList[target][1] == '@') {
11749             if (appData.highlightLastMove) {
11750                 SetHighlights(-1, -1, toX, toY);
11751             }
11752         } else {
11753             fromX = moveList[target][0] - AAA;
11754             fromY = moveList[target][1] - ONE;
11755             if (target == currentMove - 1) {
11756                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11757             }
11758             if (appData.highlightLastMove) {
11759                 SetHighlights(fromX, fromY, toX, toY);
11760             }
11761         }
11762     }
11763     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11764         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11765         while (currentMove > target) {
11766             SendToProgram("undo\n", &first);
11767             currentMove--;
11768         }
11769     } else {
11770         currentMove = target;
11771     }
11772     
11773     if (gameMode == EditGame || gameMode == EndOfGame) {
11774         whiteTimeRemaining = timeRemaining[0][currentMove];
11775         blackTimeRemaining = timeRemaining[1][currentMove];
11776     }
11777     DisplayBothClocks();
11778     DisplayMove(currentMove - 1);
11779     DrawPosition(full_redraw, boards[currentMove]);
11780     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11781     // [HGM] PV info: routine tests if comment empty
11782     DisplayComment(currentMove - 1, commentList[currentMove]);
11783 }
11784
11785 void
11786 BackwardEvent()
11787 {
11788     if (gameMode == IcsExamining && !pausing) {
11789         SendToICS(ics_prefix);
11790         SendToICS("backward\n");
11791     } else {
11792         BackwardInner(currentMove - 1);
11793     }
11794 }
11795
11796 void
11797 ToStartEvent()
11798 {
11799     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11800         /* to optimize, we temporarily turn off analysis mode while we undo
11801          * all the moves. Otherwise we get analysis output after each undo.
11802          */ 
11803         if (first.analysisSupport) {
11804           SendToProgram("exit\nforce\n", &first);
11805           first.analyzing = FALSE;
11806         }
11807     }
11808
11809     if (gameMode == IcsExamining && !pausing) {
11810         SendToICS(ics_prefix);
11811         SendToICS("backward 999999\n");
11812     } else {
11813         BackwardInner(backwardMostMove);
11814     }
11815
11816     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11817         /* we have fed all the moves, so reactivate analysis mode */
11818         SendToProgram("analyze\n", &first);
11819         first.analyzing = TRUE;
11820         /*first.maybeThinking = TRUE;*/
11821         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11822     }
11823 }
11824
11825 void
11826 ToNrEvent(int to)
11827 {
11828   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11829   if (to >= forwardMostMove) to = forwardMostMove;
11830   if (to <= backwardMostMove) to = backwardMostMove;
11831   if (to < currentMove) {
11832     BackwardInner(to);
11833   } else {
11834     ForwardInner(to);
11835   }
11836 }
11837
11838 void
11839 RevertEvent()
11840 {
11841     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11842         return;
11843     }
11844     if (gameMode != IcsExamining) {
11845         DisplayError(_("You are not examining a game"), 0);
11846         return;
11847     }
11848     if (pausing) {
11849         DisplayError(_("You can't revert while pausing"), 0);
11850         return;
11851     }
11852     SendToICS(ics_prefix);
11853     SendToICS("revert\n");
11854 }
11855
11856 void
11857 RetractMoveEvent()
11858 {
11859     switch (gameMode) {
11860       case MachinePlaysWhite:
11861       case MachinePlaysBlack:
11862         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11863             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11864             return;
11865         }
11866         if (forwardMostMove < 2) return;
11867         currentMove = forwardMostMove = forwardMostMove - 2;
11868         whiteTimeRemaining = timeRemaining[0][currentMove];
11869         blackTimeRemaining = timeRemaining[1][currentMove];
11870         DisplayBothClocks();
11871         DisplayMove(currentMove - 1);
11872         ClearHighlights();/*!! could figure this out*/
11873         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11874         SendToProgram("remove\n", &first);
11875         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11876         break;
11877
11878       case BeginningOfGame:
11879       default:
11880         break;
11881
11882       case IcsPlayingWhite:
11883       case IcsPlayingBlack:
11884         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11885             SendToICS(ics_prefix);
11886             SendToICS("takeback 2\n");
11887         } else {
11888             SendToICS(ics_prefix);
11889             SendToICS("takeback 1\n");
11890         }
11891         break;
11892     }
11893 }
11894
11895 void
11896 MoveNowEvent()
11897 {
11898     ChessProgramState *cps;
11899
11900     switch (gameMode) {
11901       case MachinePlaysWhite:
11902         if (!WhiteOnMove(forwardMostMove)) {
11903             DisplayError(_("It is your turn"), 0);
11904             return;
11905         }
11906         cps = &first;
11907         break;
11908       case MachinePlaysBlack:
11909         if (WhiteOnMove(forwardMostMove)) {
11910             DisplayError(_("It is your turn"), 0);
11911             return;
11912         }
11913         cps = &first;
11914         break;
11915       case TwoMachinesPlay:
11916         if (WhiteOnMove(forwardMostMove) ==
11917             (first.twoMachinesColor[0] == 'w')) {
11918             cps = &first;
11919         } else {
11920             cps = &second;
11921         }
11922         break;
11923       case BeginningOfGame:
11924       default:
11925         return;
11926     }
11927     SendToProgram("?\n", cps);
11928 }
11929
11930 void
11931 TruncateGameEvent()
11932 {
11933     EditGameEvent();
11934     if (gameMode != EditGame) return;
11935     TruncateGame();
11936 }
11937
11938 void
11939 TruncateGame()
11940 {
11941     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11942     if (forwardMostMove > currentMove) {
11943         if (gameInfo.resultDetails != NULL) {
11944             free(gameInfo.resultDetails);
11945             gameInfo.resultDetails = NULL;
11946             gameInfo.result = GameUnfinished;
11947         }
11948         forwardMostMove = currentMove;
11949         HistorySet(parseList, backwardMostMove, forwardMostMove,
11950                    currentMove-1);
11951     }
11952 }
11953
11954 void
11955 HintEvent()
11956 {
11957     if (appData.noChessProgram) return;
11958     switch (gameMode) {
11959       case MachinePlaysWhite:
11960         if (WhiteOnMove(forwardMostMove)) {
11961             DisplayError(_("Wait until your turn"), 0);
11962             return;
11963         }
11964         break;
11965       case BeginningOfGame:
11966       case MachinePlaysBlack:
11967         if (!WhiteOnMove(forwardMostMove)) {
11968             DisplayError(_("Wait until your turn"), 0);
11969             return;
11970         }
11971         break;
11972       default:
11973         DisplayError(_("No hint available"), 0);
11974         return;
11975     }
11976     SendToProgram("hint\n", &first);
11977     hintRequested = TRUE;
11978 }
11979
11980 void
11981 BookEvent()
11982 {
11983     if (appData.noChessProgram) return;
11984     switch (gameMode) {
11985       case MachinePlaysWhite:
11986         if (WhiteOnMove(forwardMostMove)) {
11987             DisplayError(_("Wait until your turn"), 0);
11988             return;
11989         }
11990         break;
11991       case BeginningOfGame:
11992       case MachinePlaysBlack:
11993         if (!WhiteOnMove(forwardMostMove)) {
11994             DisplayError(_("Wait until your turn"), 0);
11995             return;
11996         }
11997         break;
11998       case EditPosition:
11999         EditPositionDone(TRUE);
12000         break;
12001       case TwoMachinesPlay:
12002         return;
12003       default:
12004         break;
12005     }
12006     SendToProgram("bk\n", &first);
12007     bookOutput[0] = NULLCHAR;
12008     bookRequested = TRUE;
12009 }
12010
12011 void
12012 AboutGameEvent()
12013 {
12014     char *tags = PGNTags(&gameInfo);
12015     TagsPopUp(tags, CmailMsg());
12016     free(tags);
12017 }
12018
12019 /* end button procedures */
12020
12021 void
12022 PrintPosition(fp, move)
12023      FILE *fp;
12024      int move;
12025 {
12026     int i, j;
12027     
12028     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12029         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12030             char c = PieceToChar(boards[move][i][j]);
12031             fputc(c == 'x' ? '.' : c, fp);
12032             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12033         }
12034     }
12035     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12036       fprintf(fp, "white to play\n");
12037     else
12038       fprintf(fp, "black to play\n");
12039 }
12040
12041 void
12042 PrintOpponents(fp)
12043      FILE *fp;
12044 {
12045     if (gameInfo.white != NULL) {
12046         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12047     } else {
12048         fprintf(fp, "\n");
12049     }
12050 }
12051
12052 /* Find last component of program's own name, using some heuristics */
12053 void
12054 TidyProgramName(prog, host, buf)
12055      char *prog, *host, buf[MSG_SIZ];
12056 {
12057     char *p, *q;
12058     int local = (strcmp(host, "localhost") == 0);
12059     while (!local && (p = strchr(prog, ';')) != NULL) {
12060         p++;
12061         while (*p == ' ') p++;
12062         prog = p;
12063     }
12064     if (*prog == '"' || *prog == '\'') {
12065         q = strchr(prog + 1, *prog);
12066     } else {
12067         q = strchr(prog, ' ');
12068     }
12069     if (q == NULL) q = prog + strlen(prog);
12070     p = q;
12071     while (p >= prog && *p != '/' && *p != '\\') p--;
12072     p++;
12073     if(p == prog && *p == '"') p++;
12074     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12075     memcpy(buf, p, q - p);
12076     buf[q - p] = NULLCHAR;
12077     if (!local) {
12078         strcat(buf, "@");
12079         strcat(buf, host);
12080     }
12081 }
12082
12083 char *
12084 TimeControlTagValue()
12085 {
12086     char buf[MSG_SIZ];
12087     if (!appData.clockMode) {
12088         strcpy(buf, "-");
12089     } else if (movesPerSession > 0) {
12090         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12091     } else if (timeIncrement == 0) {
12092         sprintf(buf, "%ld", timeControl/1000);
12093     } else {
12094         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12095     }
12096     return StrSave(buf);
12097 }
12098
12099 void
12100 SetGameInfo()
12101 {
12102     /* This routine is used only for certain modes */
12103     VariantClass v = gameInfo.variant;
12104     ChessMove r = GameUnfinished;
12105     char *p = NULL;
12106
12107     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12108         r = gameInfo.result; 
12109         p = gameInfo.resultDetails; 
12110         gameInfo.resultDetails = NULL;
12111     }
12112     ClearGameInfo(&gameInfo);
12113     gameInfo.variant = v;
12114
12115     switch (gameMode) {
12116       case MachinePlaysWhite:
12117         gameInfo.event = StrSave( appData.pgnEventHeader );
12118         gameInfo.site = StrSave(HostName());
12119         gameInfo.date = PGNDate();
12120         gameInfo.round = StrSave("-");
12121         gameInfo.white = StrSave(first.tidy);
12122         gameInfo.black = StrSave(UserName());
12123         gameInfo.timeControl = TimeControlTagValue();
12124         break;
12125
12126       case MachinePlaysBlack:
12127         gameInfo.event = StrSave( appData.pgnEventHeader );
12128         gameInfo.site = StrSave(HostName());
12129         gameInfo.date = PGNDate();
12130         gameInfo.round = StrSave("-");
12131         gameInfo.white = StrSave(UserName());
12132         gameInfo.black = StrSave(first.tidy);
12133         gameInfo.timeControl = TimeControlTagValue();
12134         break;
12135
12136       case TwoMachinesPlay:
12137         gameInfo.event = StrSave( appData.pgnEventHeader );
12138         gameInfo.site = StrSave(HostName());
12139         gameInfo.date = PGNDate();
12140         if (matchGame > 0) {
12141             char buf[MSG_SIZ];
12142             sprintf(buf, "%d", matchGame);
12143             gameInfo.round = StrSave(buf);
12144         } else {
12145             gameInfo.round = StrSave("-");
12146         }
12147         if (first.twoMachinesColor[0] == 'w') {
12148             gameInfo.white = StrSave(first.tidy);
12149             gameInfo.black = StrSave(second.tidy);
12150         } else {
12151             gameInfo.white = StrSave(second.tidy);
12152             gameInfo.black = StrSave(first.tidy);
12153         }
12154         gameInfo.timeControl = TimeControlTagValue();
12155         break;
12156
12157       case EditGame:
12158         gameInfo.event = StrSave("Edited game");
12159         gameInfo.site = StrSave(HostName());
12160         gameInfo.date = PGNDate();
12161         gameInfo.round = StrSave("-");
12162         gameInfo.white = StrSave("-");
12163         gameInfo.black = StrSave("-");
12164         gameInfo.result = r;
12165         gameInfo.resultDetails = p;
12166         break;
12167
12168       case EditPosition:
12169         gameInfo.event = StrSave("Edited position");
12170         gameInfo.site = StrSave(HostName());
12171         gameInfo.date = PGNDate();
12172         gameInfo.round = StrSave("-");
12173         gameInfo.white = StrSave("-");
12174         gameInfo.black = StrSave("-");
12175         break;
12176
12177       case IcsPlayingWhite:
12178       case IcsPlayingBlack:
12179       case IcsObserving:
12180       case IcsExamining:
12181         break;
12182
12183       case PlayFromGameFile:
12184         gameInfo.event = StrSave("Game from non-PGN file");
12185         gameInfo.site = StrSave(HostName());
12186         gameInfo.date = PGNDate();
12187         gameInfo.round = StrSave("-");
12188         gameInfo.white = StrSave("?");
12189         gameInfo.black = StrSave("?");
12190         break;
12191
12192       default:
12193         break;
12194     }
12195 }
12196
12197 void
12198 ReplaceComment(index, text)
12199      int index;
12200      char *text;
12201 {
12202     int len;
12203
12204     while (*text == '\n') text++;
12205     len = strlen(text);
12206     while (len > 0 && text[len - 1] == '\n') len--;
12207
12208     if (commentList[index] != NULL)
12209       free(commentList[index]);
12210
12211     if (len == 0) {
12212         commentList[index] = NULL;
12213         return;
12214     }
12215   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12216       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12217       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12218     commentList[index] = (char *) malloc(len + 2);
12219     strncpy(commentList[index], text, len);
12220     commentList[index][len] = '\n';
12221     commentList[index][len + 1] = NULLCHAR;
12222   } else { 
12223     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12224     char *p;
12225     commentList[index] = (char *) malloc(len + 6);
12226     strcpy(commentList[index], "{\n");
12227     strncpy(commentList[index]+2, text, len);
12228     commentList[index][len+2] = NULLCHAR;
12229     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12230     strcat(commentList[index], "\n}\n");
12231   }
12232 }
12233
12234 void
12235 CrushCRs(text)
12236      char *text;
12237 {
12238   char *p = text;
12239   char *q = text;
12240   char ch;
12241
12242   do {
12243     ch = *p++;
12244     if (ch == '\r') continue;
12245     *q++ = ch;
12246   } while (ch != '\0');
12247 }
12248
12249 void
12250 AppendComment(index, text, addBraces)
12251      int index;
12252      char *text;
12253      Boolean addBraces; // [HGM] braces: tells if we should add {}
12254 {
12255     int oldlen, len;
12256     char *old;
12257
12258 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12259     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12260
12261     CrushCRs(text);
12262     while (*text == '\n') text++;
12263     len = strlen(text);
12264     while (len > 0 && text[len - 1] == '\n') len--;
12265
12266     if (len == 0) return;
12267
12268     if (commentList[index] != NULL) {
12269         old = commentList[index];
12270         oldlen = strlen(old);
12271         while(commentList[index][oldlen-1] ==  '\n')
12272           commentList[index][--oldlen] = NULLCHAR;
12273         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12274         strcpy(commentList[index], old);
12275         free(old);
12276         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12277         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12278           if(addBraces) addBraces = FALSE; else { text++; len--; }
12279           while (*text == '\n') { text++; len--; }
12280           commentList[index][--oldlen] = NULLCHAR;
12281       }
12282         if(addBraces) strcat(commentList[index], "\n{\n");
12283         else          strcat(commentList[index], "\n");
12284         strcat(commentList[index], text);
12285         if(addBraces) strcat(commentList[index], "\n}\n");
12286         else          strcat(commentList[index], "\n");
12287     } else {
12288         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12289         if(addBraces)
12290              strcpy(commentList[index], "{\n");
12291         else commentList[index][0] = NULLCHAR;
12292         strcat(commentList[index], text);
12293         strcat(commentList[index], "\n");
12294         if(addBraces) strcat(commentList[index], "}\n");
12295     }
12296 }
12297
12298 static char * FindStr( char * text, char * sub_text )
12299 {
12300     char * result = strstr( text, sub_text );
12301
12302     if( result != NULL ) {
12303         result += strlen( sub_text );
12304     }
12305
12306     return result;
12307 }
12308
12309 /* [AS] Try to extract PV info from PGN comment */
12310 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12311 char *GetInfoFromComment( int index, char * text )
12312 {
12313     char * sep = text;
12314
12315     if( text != NULL && index > 0 ) {
12316         int score = 0;
12317         int depth = 0;
12318         int time = -1, sec = 0, deci;
12319         char * s_eval = FindStr( text, "[%eval " );
12320         char * s_emt = FindStr( text, "[%emt " );
12321
12322         if( s_eval != NULL || s_emt != NULL ) {
12323             /* New style */
12324             char delim;
12325
12326             if( s_eval != NULL ) {
12327                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12328                     return text;
12329                 }
12330
12331                 if( delim != ']' ) {
12332                     return text;
12333                 }
12334             }
12335
12336             if( s_emt != NULL ) {
12337             }
12338                 return text;
12339         }
12340         else {
12341             /* We expect something like: [+|-]nnn.nn/dd */
12342             int score_lo = 0;
12343
12344             if(*text != '{') return text; // [HGM] braces: must be normal comment
12345
12346             sep = strchr( text, '/' );
12347             if( sep == NULL || sep < (text+4) ) {
12348                 return text;
12349             }
12350
12351             time = -1; sec = -1; deci = -1;
12352             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12353                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12354                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12355                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12356                 return text;
12357             }
12358
12359             if( score_lo < 0 || score_lo >= 100 ) {
12360                 return text;
12361             }
12362
12363             if(sec >= 0) time = 600*time + 10*sec; else
12364             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12365
12366             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12367
12368             /* [HGM] PV time: now locate end of PV info */
12369             while( *++sep >= '0' && *sep <= '9'); // strip depth
12370             if(time >= 0)
12371             while( *++sep >= '0' && *sep <= '9'); // strip time
12372             if(sec >= 0)
12373             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12374             if(deci >= 0)
12375             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12376             while(*sep == ' ') sep++;
12377         }
12378
12379         if( depth <= 0 ) {
12380             return text;
12381         }
12382
12383         if( time < 0 ) {
12384             time = -1;
12385         }
12386
12387         pvInfoList[index-1].depth = depth;
12388         pvInfoList[index-1].score = score;
12389         pvInfoList[index-1].time  = 10*time; // centi-sec
12390         if(*sep == '}') *sep = 0; else *--sep = '{';
12391     }
12392     return sep;
12393 }
12394
12395 void
12396 SendToProgram(message, cps)
12397      char *message;
12398      ChessProgramState *cps;
12399 {
12400     int count, outCount, error;
12401     char buf[MSG_SIZ];
12402
12403     if (cps->pr == NULL) return;
12404     Attention(cps);
12405     
12406     if (appData.debugMode) {
12407         TimeMark now;
12408         GetTimeMark(&now);
12409         fprintf(debugFP, "%ld >%-6s: %s", 
12410                 SubtractTimeMarks(&now, &programStartTime),
12411                 cps->which, message);
12412     }
12413     
12414     count = strlen(message);
12415     outCount = OutputToProcess(cps->pr, message, count, &error);
12416     if (outCount < count && !exiting 
12417                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12418         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12419         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12420             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12421                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12422                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12423             } else {
12424                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12425             }
12426             gameInfo.resultDetails = StrSave(buf);
12427         }
12428         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12429     }
12430 }
12431
12432 void
12433 ReceiveFromProgram(isr, closure, message, count, error)
12434      InputSourceRef isr;
12435      VOIDSTAR closure;
12436      char *message;
12437      int count;
12438      int error;
12439 {
12440     char *end_str;
12441     char buf[MSG_SIZ];
12442     ChessProgramState *cps = (ChessProgramState *)closure;
12443
12444     if (isr != cps->isr) return; /* Killed intentionally */
12445     if (count <= 0) {
12446         if (count == 0) {
12447             sprintf(buf,
12448                     _("Error: %s chess program (%s) exited unexpectedly"),
12449                     cps->which, cps->program);
12450         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12451                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12452                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12453                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12454                 } else {
12455                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12456                 }
12457                 gameInfo.resultDetails = StrSave(buf);
12458             }
12459             RemoveInputSource(cps->isr);
12460             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12461         } else {
12462             sprintf(buf,
12463                     _("Error reading from %s chess program (%s)"),
12464                     cps->which, cps->program);
12465             RemoveInputSource(cps->isr);
12466
12467             /* [AS] Program is misbehaving badly... kill it */
12468             if( count == -2 ) {
12469                 DestroyChildProcess( cps->pr, 9 );
12470                 cps->pr = NoProc;
12471             }
12472
12473             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12474         }
12475         return;
12476     }
12477     
12478     if ((end_str = strchr(message, '\r')) != NULL)
12479       *end_str = NULLCHAR;
12480     if ((end_str = strchr(message, '\n')) != NULL)
12481       *end_str = NULLCHAR;
12482     
12483     if (appData.debugMode) {
12484         TimeMark now; int print = 1;
12485         char *quote = ""; char c; int i;
12486
12487         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12488                 char start = message[0];
12489                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12490                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12491                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12492                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12493                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12494                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12495                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12496                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12497                         { quote = "# "; print = (appData.engineComments == 2); }
12498                 message[0] = start; // restore original message
12499         }
12500         if(print) {
12501                 GetTimeMark(&now);
12502                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12503                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12504                         quote,
12505                         message);
12506         }
12507     }
12508
12509     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12510     if (appData.icsEngineAnalyze) {
12511         if (strstr(message, "whisper") != NULL ||
12512              strstr(message, "kibitz") != NULL || 
12513             strstr(message, "tellics") != NULL) return;
12514     }
12515
12516     HandleMachineMove(message, cps);
12517 }
12518
12519
12520 void
12521 SendTimeControl(cps, mps, tc, inc, sd, st)
12522      ChessProgramState *cps;
12523      int mps, inc, sd, st;
12524      long tc;
12525 {
12526     char buf[MSG_SIZ];
12527     int seconds;
12528
12529     if( timeControl_2 > 0 ) {
12530         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12531             tc = timeControl_2;
12532         }
12533     }
12534     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12535     inc /= cps->timeOdds;
12536     st  /= cps->timeOdds;
12537
12538     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12539
12540     if (st > 0) {
12541       /* Set exact time per move, normally using st command */
12542       if (cps->stKludge) {
12543         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12544         seconds = st % 60;
12545         if (seconds == 0) {
12546           sprintf(buf, "level 1 %d\n", st/60);
12547         } else {
12548           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12549         }
12550       } else {
12551         sprintf(buf, "st %d\n", st);
12552       }
12553     } else {
12554       /* Set conventional or incremental time control, using level command */
12555       if (seconds == 0) {
12556         /* Note old gnuchess bug -- minutes:seconds used to not work.
12557            Fixed in later versions, but still avoid :seconds
12558            when seconds is 0. */
12559         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12560       } else {
12561         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12562                 seconds, inc/1000);
12563       }
12564     }
12565     SendToProgram(buf, cps);
12566
12567     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12568     /* Orthogonally, limit search to given depth */
12569     if (sd > 0) {
12570       if (cps->sdKludge) {
12571         sprintf(buf, "depth\n%d\n", sd);
12572       } else {
12573         sprintf(buf, "sd %d\n", sd);
12574       }
12575       SendToProgram(buf, cps);
12576     }
12577
12578     if(cps->nps > 0) { /* [HGM] nps */
12579         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12580         else {
12581                 sprintf(buf, "nps %d\n", cps->nps);
12582               SendToProgram(buf, cps);
12583         }
12584     }
12585 }
12586
12587 ChessProgramState *WhitePlayer()
12588 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12589 {
12590     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12591        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12592         return &second;
12593     return &first;
12594 }
12595
12596 void
12597 SendTimeRemaining(cps, machineWhite)
12598      ChessProgramState *cps;
12599      int /*boolean*/ machineWhite;
12600 {
12601     char message[MSG_SIZ];
12602     long time, otime;
12603
12604     /* Note: this routine must be called when the clocks are stopped
12605        or when they have *just* been set or switched; otherwise
12606        it will be off by the time since the current tick started.
12607     */
12608     if (machineWhite) {
12609         time = whiteTimeRemaining / 10;
12610         otime = blackTimeRemaining / 10;
12611     } else {
12612         time = blackTimeRemaining / 10;
12613         otime = whiteTimeRemaining / 10;
12614     }
12615     /* [HGM] translate opponent's time by time-odds factor */
12616     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12617     if (appData.debugMode) {
12618         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12619     }
12620
12621     if (time <= 0) time = 1;
12622     if (otime <= 0) otime = 1;
12623     
12624     sprintf(message, "time %ld\n", time);
12625     SendToProgram(message, cps);
12626
12627     sprintf(message, "otim %ld\n", otime);
12628     SendToProgram(message, cps);
12629 }
12630
12631 int
12632 BoolFeature(p, name, loc, cps)
12633      char **p;
12634      char *name;
12635      int *loc;
12636      ChessProgramState *cps;
12637 {
12638   char buf[MSG_SIZ];
12639   int len = strlen(name);
12640   int val;
12641   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12642     (*p) += len + 1;
12643     sscanf(*p, "%d", &val);
12644     *loc = (val != 0);
12645     while (**p && **p != ' ') (*p)++;
12646     sprintf(buf, "accepted %s\n", name);
12647     SendToProgram(buf, cps);
12648     return TRUE;
12649   }
12650   return FALSE;
12651 }
12652
12653 int
12654 IntFeature(p, name, loc, cps)
12655      char **p;
12656      char *name;
12657      int *loc;
12658      ChessProgramState *cps;
12659 {
12660   char buf[MSG_SIZ];
12661   int len = strlen(name);
12662   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12663     (*p) += len + 1;
12664     sscanf(*p, "%d", loc);
12665     while (**p && **p != ' ') (*p)++;
12666     sprintf(buf, "accepted %s\n", name);
12667     SendToProgram(buf, cps);
12668     return TRUE;
12669   }
12670   return FALSE;
12671 }
12672
12673 int
12674 StringFeature(p, name, loc, cps)
12675      char **p;
12676      char *name;
12677      char loc[];
12678      ChessProgramState *cps;
12679 {
12680   char buf[MSG_SIZ];
12681   int len = strlen(name);
12682   if (strncmp((*p), name, len) == 0
12683       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12684     (*p) += len + 2;
12685     sscanf(*p, "%[^\"]", loc);
12686     while (**p && **p != '\"') (*p)++;
12687     if (**p == '\"') (*p)++;
12688     sprintf(buf, "accepted %s\n", name);
12689     SendToProgram(buf, cps);
12690     return TRUE;
12691   }
12692   return FALSE;
12693 }
12694
12695 int 
12696 ParseOption(Option *opt, ChessProgramState *cps)
12697 // [HGM] options: process the string that defines an engine option, and determine
12698 // name, type, default value, and allowed value range
12699 {
12700         char *p, *q, buf[MSG_SIZ];
12701         int n, min = (-1)<<31, max = 1<<31, def;
12702
12703         if(p = strstr(opt->name, " -spin ")) {
12704             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12705             if(max < min) max = min; // enforce consistency
12706             if(def < min) def = min;
12707             if(def > max) def = max;
12708             opt->value = def;
12709             opt->min = min;
12710             opt->max = max;
12711             opt->type = Spin;
12712         } else if((p = strstr(opt->name, " -slider "))) {
12713             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12714             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12715             if(max < min) max = min; // enforce consistency
12716             if(def < min) def = min;
12717             if(def > max) def = max;
12718             opt->value = def;
12719             opt->min = min;
12720             opt->max = max;
12721             opt->type = Spin; // Slider;
12722         } else if((p = strstr(opt->name, " -string "))) {
12723             opt->textValue = p+9;
12724             opt->type = TextBox;
12725         } else if((p = strstr(opt->name, " -file "))) {
12726             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12727             opt->textValue = p+7;
12728             opt->type = TextBox; // FileName;
12729         } else if((p = strstr(opt->name, " -path "))) {
12730             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12731             opt->textValue = p+7;
12732             opt->type = TextBox; // PathName;
12733         } else if(p = strstr(opt->name, " -check ")) {
12734             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12735             opt->value = (def != 0);
12736             opt->type = CheckBox;
12737         } else if(p = strstr(opt->name, " -combo ")) {
12738             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12739             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12740             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12741             opt->value = n = 0;
12742             while(q = StrStr(q, " /// ")) {
12743                 n++; *q = 0;    // count choices, and null-terminate each of them
12744                 q += 5;
12745                 if(*q == '*') { // remember default, which is marked with * prefix
12746                     q++;
12747                     opt->value = n;
12748                 }
12749                 cps->comboList[cps->comboCnt++] = q;
12750             }
12751             cps->comboList[cps->comboCnt++] = NULL;
12752             opt->max = n + 1;
12753             opt->type = ComboBox;
12754         } else if(p = strstr(opt->name, " -button")) {
12755             opt->type = Button;
12756         } else if(p = strstr(opt->name, " -save")) {
12757             opt->type = SaveButton;
12758         } else return FALSE;
12759         *p = 0; // terminate option name
12760         // now look if the command-line options define a setting for this engine option.
12761         if(cps->optionSettings && cps->optionSettings[0])
12762             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12763         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12764                 sprintf(buf, "option %s", p);
12765                 if(p = strstr(buf, ",")) *p = 0;
12766                 strcat(buf, "\n");
12767                 SendToProgram(buf, cps);
12768         }
12769         return TRUE;
12770 }
12771
12772 void
12773 FeatureDone(cps, val)
12774      ChessProgramState* cps;
12775      int val;
12776 {
12777   DelayedEventCallback cb = GetDelayedEvent();
12778   if ((cb == InitBackEnd3 && cps == &first) ||
12779       (cb == TwoMachinesEventIfReady && cps == &second)) {
12780     CancelDelayedEvent();
12781     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12782   }
12783   cps->initDone = val;
12784 }
12785
12786 /* Parse feature command from engine */
12787 void
12788 ParseFeatures(args, cps)
12789      char* args;
12790      ChessProgramState *cps;  
12791 {
12792   char *p = args;
12793   char *q;
12794   int val;
12795   char buf[MSG_SIZ];
12796
12797   for (;;) {
12798     while (*p == ' ') p++;
12799     if (*p == NULLCHAR) return;
12800
12801     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12802     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12803     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12804     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12805     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12806     if (BoolFeature(&p, "reuse", &val, cps)) {
12807       /* Engine can disable reuse, but can't enable it if user said no */
12808       if (!val) cps->reuse = FALSE;
12809       continue;
12810     }
12811     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12812     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12813       if (gameMode == TwoMachinesPlay) {
12814         DisplayTwoMachinesTitle();
12815       } else {
12816         DisplayTitle("");
12817       }
12818       continue;
12819     }
12820     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12821     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12822     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12823     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12824     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12825     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12826     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12827     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12828     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12829     if (IntFeature(&p, "done", &val, cps)) {
12830       FeatureDone(cps, val);
12831       continue;
12832     }
12833     /* Added by Tord: */
12834     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12835     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12836     /* End of additions by Tord */
12837
12838     /* [HGM] added features: */
12839     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12840     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12841     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12842     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12843     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12844     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12845     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12846         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12847             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12848             SendToProgram(buf, cps);
12849             continue;
12850         }
12851         if(cps->nrOptions >= MAX_OPTIONS) {
12852             cps->nrOptions--;
12853             sprintf(buf, "%s engine has too many options\n", cps->which);
12854             DisplayError(buf, 0);
12855         }
12856         continue;
12857     }
12858     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12859     /* End of additions by HGM */
12860
12861     /* unknown feature: complain and skip */
12862     q = p;
12863     while (*q && *q != '=') q++;
12864     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12865     SendToProgram(buf, cps);
12866     p = q;
12867     if (*p == '=') {
12868       p++;
12869       if (*p == '\"') {
12870         p++;
12871         while (*p && *p != '\"') p++;
12872         if (*p == '\"') p++;
12873       } else {
12874         while (*p && *p != ' ') p++;
12875       }
12876     }
12877   }
12878
12879 }
12880
12881 void
12882 PeriodicUpdatesEvent(newState)
12883      int newState;
12884 {
12885     if (newState == appData.periodicUpdates)
12886       return;
12887
12888     appData.periodicUpdates=newState;
12889
12890     /* Display type changes, so update it now */
12891 //    DisplayAnalysis();
12892
12893     /* Get the ball rolling again... */
12894     if (newState) {
12895         AnalysisPeriodicEvent(1);
12896         StartAnalysisClock();
12897     }
12898 }
12899
12900 void
12901 PonderNextMoveEvent(newState)
12902      int newState;
12903 {
12904     if (newState == appData.ponderNextMove) return;
12905     if (gameMode == EditPosition) EditPositionDone(TRUE);
12906     if (newState) {
12907         SendToProgram("hard\n", &first);
12908         if (gameMode == TwoMachinesPlay) {
12909             SendToProgram("hard\n", &second);
12910         }
12911     } else {
12912         SendToProgram("easy\n", &first);
12913         thinkOutput[0] = NULLCHAR;
12914         if (gameMode == TwoMachinesPlay) {
12915             SendToProgram("easy\n", &second);
12916         }
12917     }
12918     appData.ponderNextMove = newState;
12919 }
12920
12921 void
12922 NewSettingEvent(option, command, value)
12923      char *command;
12924      int option, value;
12925 {
12926     char buf[MSG_SIZ];
12927
12928     if (gameMode == EditPosition) EditPositionDone(TRUE);
12929     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12930     SendToProgram(buf, &first);
12931     if (gameMode == TwoMachinesPlay) {
12932         SendToProgram(buf, &second);
12933     }
12934 }
12935
12936 void
12937 ShowThinkingEvent()
12938 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12939 {
12940     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12941     int newState = appData.showThinking
12942         // [HGM] thinking: other features now need thinking output as well
12943         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12944     
12945     if (oldState == newState) return;
12946     oldState = newState;
12947     if (gameMode == EditPosition) EditPositionDone(TRUE);
12948     if (oldState) {
12949         SendToProgram("post\n", &first);
12950         if (gameMode == TwoMachinesPlay) {
12951             SendToProgram("post\n", &second);
12952         }
12953     } else {
12954         SendToProgram("nopost\n", &first);
12955         thinkOutput[0] = NULLCHAR;
12956         if (gameMode == TwoMachinesPlay) {
12957             SendToProgram("nopost\n", &second);
12958         }
12959     }
12960 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12961 }
12962
12963 void
12964 AskQuestionEvent(title, question, replyPrefix, which)
12965      char *title; char *question; char *replyPrefix; char *which;
12966 {
12967   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12968   if (pr == NoProc) return;
12969   AskQuestion(title, question, replyPrefix, pr);
12970 }
12971
12972 void
12973 DisplayMove(moveNumber)
12974      int moveNumber;
12975 {
12976     char message[MSG_SIZ];
12977     char res[MSG_SIZ];
12978     char cpThinkOutput[MSG_SIZ];
12979
12980     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12981     
12982     if (moveNumber == forwardMostMove - 1 || 
12983         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12984
12985         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12986
12987         if (strchr(cpThinkOutput, '\n')) {
12988             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12989         }
12990     } else {
12991         *cpThinkOutput = NULLCHAR;
12992     }
12993
12994     /* [AS] Hide thinking from human user */
12995     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12996         *cpThinkOutput = NULLCHAR;
12997         if( thinkOutput[0] != NULLCHAR ) {
12998             int i;
12999
13000             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13001                 cpThinkOutput[i] = '.';
13002             }
13003             cpThinkOutput[i] = NULLCHAR;
13004             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13005         }
13006     }
13007
13008     if (moveNumber == forwardMostMove - 1 &&
13009         gameInfo.resultDetails != NULL) {
13010         if (gameInfo.resultDetails[0] == NULLCHAR) {
13011             sprintf(res, " %s", PGNResult(gameInfo.result));
13012         } else {
13013             sprintf(res, " {%s} %s",
13014                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13015         }
13016     } else {
13017         res[0] = NULLCHAR;
13018     }
13019
13020     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13021         DisplayMessage(res, cpThinkOutput);
13022     } else {
13023         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13024                 WhiteOnMove(moveNumber) ? " " : ".. ",
13025                 parseList[moveNumber], res);
13026         DisplayMessage(message, cpThinkOutput);
13027     }
13028 }
13029
13030 void
13031 DisplayComment(moveNumber, text)
13032      int moveNumber;
13033      char *text;
13034 {
13035     char title[MSG_SIZ];
13036     char buf[8000]; // comment can be long!
13037     int score, depth;
13038     
13039     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13040       strcpy(title, "Comment");
13041     } else {
13042       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13043               WhiteOnMove(moveNumber) ? " " : ".. ",
13044               parseList[moveNumber]);
13045     }
13046     // [HGM] PV info: display PV info together with (or as) comment
13047     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13048       if(text == NULL) text = "";                                           
13049       score = pvInfoList[moveNumber].score;
13050       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13051               depth, (pvInfoList[moveNumber].time+50)/100, text);
13052       text = buf;
13053     }
13054     if (text != NULL && (appData.autoDisplayComment || commentUp))
13055         CommentPopUp(title, text);
13056 }
13057
13058 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13059  * might be busy thinking or pondering.  It can be omitted if your
13060  * gnuchess is configured to stop thinking immediately on any user
13061  * input.  However, that gnuchess feature depends on the FIONREAD
13062  * ioctl, which does not work properly on some flavors of Unix.
13063  */
13064 void
13065 Attention(cps)
13066      ChessProgramState *cps;
13067 {
13068 #if ATTENTION
13069     if (!cps->useSigint) return;
13070     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13071     switch (gameMode) {
13072       case MachinePlaysWhite:
13073       case MachinePlaysBlack:
13074       case TwoMachinesPlay:
13075       case IcsPlayingWhite:
13076       case IcsPlayingBlack:
13077       case AnalyzeMode:
13078       case AnalyzeFile:
13079         /* Skip if we know it isn't thinking */
13080         if (!cps->maybeThinking) return;
13081         if (appData.debugMode)
13082           fprintf(debugFP, "Interrupting %s\n", cps->which);
13083         InterruptChildProcess(cps->pr);
13084         cps->maybeThinking = FALSE;
13085         break;
13086       default:
13087         break;
13088     }
13089 #endif /*ATTENTION*/
13090 }
13091
13092 int
13093 CheckFlags()
13094 {
13095     if (whiteTimeRemaining <= 0) {
13096         if (!whiteFlag) {
13097             whiteFlag = TRUE;
13098             if (appData.icsActive) {
13099                 if (appData.autoCallFlag &&
13100                     gameMode == IcsPlayingBlack && !blackFlag) {
13101                   SendToICS(ics_prefix);
13102                   SendToICS("flag\n");
13103                 }
13104             } else {
13105                 if (blackFlag) {
13106                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13107                 } else {
13108                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13109                     if (appData.autoCallFlag) {
13110                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13111                         return TRUE;
13112                     }
13113                 }
13114             }
13115         }
13116     }
13117     if (blackTimeRemaining <= 0) {
13118         if (!blackFlag) {
13119             blackFlag = TRUE;
13120             if (appData.icsActive) {
13121                 if (appData.autoCallFlag &&
13122                     gameMode == IcsPlayingWhite && !whiteFlag) {
13123                   SendToICS(ics_prefix);
13124                   SendToICS("flag\n");
13125                 }
13126             } else {
13127                 if (whiteFlag) {
13128                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13129                 } else {
13130                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13131                     if (appData.autoCallFlag) {
13132                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13133                         return TRUE;
13134                     }
13135                 }
13136             }
13137         }
13138     }
13139     return FALSE;
13140 }
13141
13142 void
13143 CheckTimeControl()
13144 {
13145     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13146         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13147
13148     /*
13149      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13150      */
13151     if ( !WhiteOnMove(forwardMostMove) )
13152         /* White made time control */
13153         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13154         /* [HGM] time odds: correct new time quota for time odds! */
13155                                             / WhitePlayer()->timeOdds;
13156       else
13157         /* Black made time control */
13158         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13159                                             / WhitePlayer()->other->timeOdds;
13160 }
13161
13162 void
13163 DisplayBothClocks()
13164 {
13165     int wom = gameMode == EditPosition ?
13166       !blackPlaysFirst : WhiteOnMove(currentMove);
13167     DisplayWhiteClock(whiteTimeRemaining, wom);
13168     DisplayBlackClock(blackTimeRemaining, !wom);
13169 }
13170
13171
13172 /* Timekeeping seems to be a portability nightmare.  I think everyone
13173    has ftime(), but I'm really not sure, so I'm including some ifdefs
13174    to use other calls if you don't.  Clocks will be less accurate if
13175    you have neither ftime nor gettimeofday.
13176 */
13177
13178 /* VS 2008 requires the #include outside of the function */
13179 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13180 #include <sys/timeb.h>
13181 #endif
13182
13183 /* Get the current time as a TimeMark */
13184 void
13185 GetTimeMark(tm)
13186      TimeMark *tm;
13187 {
13188 #if HAVE_GETTIMEOFDAY
13189
13190     struct timeval timeVal;
13191     struct timezone timeZone;
13192
13193     gettimeofday(&timeVal, &timeZone);
13194     tm->sec = (long) timeVal.tv_sec; 
13195     tm->ms = (int) (timeVal.tv_usec / 1000L);
13196
13197 #else /*!HAVE_GETTIMEOFDAY*/
13198 #if HAVE_FTIME
13199
13200 // include <sys/timeb.h> / moved to just above start of function
13201     struct timeb timeB;
13202
13203     ftime(&timeB);
13204     tm->sec = (long) timeB.time;
13205     tm->ms = (int) timeB.millitm;
13206
13207 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13208     tm->sec = (long) time(NULL);
13209     tm->ms = 0;
13210 #endif
13211 #endif
13212 }
13213
13214 /* Return the difference in milliseconds between two
13215    time marks.  We assume the difference will fit in a long!
13216 */
13217 long
13218 SubtractTimeMarks(tm2, tm1)
13219      TimeMark *tm2, *tm1;
13220 {
13221     return 1000L*(tm2->sec - tm1->sec) +
13222            (long) (tm2->ms - tm1->ms);
13223 }
13224
13225
13226 /*
13227  * Code to manage the game clocks.
13228  *
13229  * In tournament play, black starts the clock and then white makes a move.
13230  * We give the human user a slight advantage if he is playing white---the
13231  * clocks don't run until he makes his first move, so it takes zero time.
13232  * Also, we don't account for network lag, so we could get out of sync
13233  * with GNU Chess's clock -- but then, referees are always right.  
13234  */
13235
13236 static TimeMark tickStartTM;
13237 static long intendedTickLength;
13238
13239 long
13240 NextTickLength(timeRemaining)
13241      long timeRemaining;
13242 {
13243     long nominalTickLength, nextTickLength;
13244
13245     if (timeRemaining > 0L && timeRemaining <= 10000L)
13246       nominalTickLength = 100L;
13247     else
13248       nominalTickLength = 1000L;
13249     nextTickLength = timeRemaining % nominalTickLength;
13250     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13251
13252     return nextTickLength;
13253 }
13254
13255 /* Adjust clock one minute up or down */
13256 void
13257 AdjustClock(Boolean which, int dir)
13258 {
13259     if(which) blackTimeRemaining += 60000*dir;
13260     else      whiteTimeRemaining += 60000*dir;
13261     DisplayBothClocks();
13262 }
13263
13264 /* Stop clocks and reset to a fresh time control */
13265 void
13266 ResetClocks() 
13267 {
13268     (void) StopClockTimer();
13269     if (appData.icsActive) {
13270         whiteTimeRemaining = blackTimeRemaining = 0;
13271     } else if (searchTime) {
13272         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13273         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13274     } else { /* [HGM] correct new time quote for time odds */
13275         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13276         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13277     }
13278     if (whiteFlag || blackFlag) {
13279         DisplayTitle("");
13280         whiteFlag = blackFlag = FALSE;
13281     }
13282     DisplayBothClocks();
13283 }
13284
13285 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13286
13287 /* Decrement running clock by amount of time that has passed */
13288 void
13289 DecrementClocks()
13290 {
13291     long timeRemaining;
13292     long lastTickLength, fudge;
13293     TimeMark now;
13294
13295     if (!appData.clockMode) return;
13296     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13297         
13298     GetTimeMark(&now);
13299
13300     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13301
13302     /* Fudge if we woke up a little too soon */
13303     fudge = intendedTickLength - lastTickLength;
13304     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13305
13306     if (WhiteOnMove(forwardMostMove)) {
13307         if(whiteNPS >= 0) lastTickLength = 0;
13308         timeRemaining = whiteTimeRemaining -= lastTickLength;
13309         DisplayWhiteClock(whiteTimeRemaining - fudge,
13310                           WhiteOnMove(currentMove));
13311     } else {
13312         if(blackNPS >= 0) lastTickLength = 0;
13313         timeRemaining = blackTimeRemaining -= lastTickLength;
13314         DisplayBlackClock(blackTimeRemaining - fudge,
13315                           !WhiteOnMove(currentMove));
13316     }
13317
13318     if (CheckFlags()) return;
13319         
13320     tickStartTM = now;
13321     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13322     StartClockTimer(intendedTickLength);
13323
13324     /* if the time remaining has fallen below the alarm threshold, sound the
13325      * alarm. if the alarm has sounded and (due to a takeback or time control
13326      * with increment) the time remaining has increased to a level above the
13327      * threshold, reset the alarm so it can sound again. 
13328      */
13329     
13330     if (appData.icsActive && appData.icsAlarm) {
13331
13332         /* make sure we are dealing with the user's clock */
13333         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13334                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13335            )) return;
13336
13337         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13338             alarmSounded = FALSE;
13339         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13340             PlayAlarmSound();
13341             alarmSounded = TRUE;
13342         }
13343     }
13344 }
13345
13346
13347 /* A player has just moved, so stop the previously running
13348    clock and (if in clock mode) start the other one.
13349    We redisplay both clocks in case we're in ICS mode, because
13350    ICS gives us an update to both clocks after every move.
13351    Note that this routine is called *after* forwardMostMove
13352    is updated, so the last fractional tick must be subtracted
13353    from the color that is *not* on move now.
13354 */
13355 void
13356 SwitchClocks()
13357 {
13358     long lastTickLength;
13359     TimeMark now;
13360     int flagged = FALSE;
13361
13362     GetTimeMark(&now);
13363
13364     if (StopClockTimer() && appData.clockMode) {
13365         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13366         if (WhiteOnMove(forwardMostMove)) {
13367             if(blackNPS >= 0) lastTickLength = 0;
13368             blackTimeRemaining -= lastTickLength;
13369            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13370 //         if(pvInfoList[forwardMostMove-1].time == -1)
13371                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13372                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13373         } else {
13374            if(whiteNPS >= 0) lastTickLength = 0;
13375            whiteTimeRemaining -= lastTickLength;
13376            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13377 //         if(pvInfoList[forwardMostMove-1].time == -1)
13378                  pvInfoList[forwardMostMove-1].time = 
13379                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13380         }
13381         flagged = CheckFlags();
13382     }
13383     CheckTimeControl();
13384
13385     if (flagged || !appData.clockMode) return;
13386
13387     switch (gameMode) {
13388       case MachinePlaysBlack:
13389       case MachinePlaysWhite:
13390       case BeginningOfGame:
13391         if (pausing) return;
13392         break;
13393
13394       case EditGame:
13395       case PlayFromGameFile:
13396       case IcsExamining:
13397         return;
13398
13399       default:
13400         break;
13401     }
13402
13403     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13404         if(WhiteOnMove(forwardMostMove))
13405              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13406         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13407     }
13408
13409     tickStartTM = now;
13410     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13411       whiteTimeRemaining : blackTimeRemaining);
13412     StartClockTimer(intendedTickLength);
13413 }
13414         
13415
13416 /* Stop both clocks */
13417 void
13418 StopClocks()
13419 {       
13420     long lastTickLength;
13421     TimeMark now;
13422
13423     if (!StopClockTimer()) return;
13424     if (!appData.clockMode) return;
13425
13426     GetTimeMark(&now);
13427
13428     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13429     if (WhiteOnMove(forwardMostMove)) {
13430         if(whiteNPS >= 0) lastTickLength = 0;
13431         whiteTimeRemaining -= lastTickLength;
13432         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13433     } else {
13434         if(blackNPS >= 0) lastTickLength = 0;
13435         blackTimeRemaining -= lastTickLength;
13436         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13437     }
13438     CheckFlags();
13439 }
13440         
13441 /* Start clock of player on move.  Time may have been reset, so
13442    if clock is already running, stop and restart it. */
13443 void
13444 StartClocks()
13445 {
13446     (void) StopClockTimer(); /* in case it was running already */
13447     DisplayBothClocks();
13448     if (CheckFlags()) return;
13449
13450     if (!appData.clockMode) return;
13451     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13452
13453     GetTimeMark(&tickStartTM);
13454     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13455       whiteTimeRemaining : blackTimeRemaining);
13456
13457    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13458     whiteNPS = blackNPS = -1; 
13459     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13460        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13461         whiteNPS = first.nps;
13462     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13463        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13464         blackNPS = first.nps;
13465     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13466         whiteNPS = second.nps;
13467     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13468         blackNPS = second.nps;
13469     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13470
13471     StartClockTimer(intendedTickLength);
13472 }
13473
13474 char *
13475 TimeString(ms)
13476      long ms;
13477 {
13478     long second, minute, hour, day;
13479     char *sign = "";
13480     static char buf[32];
13481     
13482     if (ms > 0 && ms <= 9900) {
13483       /* convert milliseconds to tenths, rounding up */
13484       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13485
13486       sprintf(buf, " %03.1f ", tenths/10.0);
13487       return buf;
13488     }
13489
13490     /* convert milliseconds to seconds, rounding up */
13491     /* use floating point to avoid strangeness of integer division
13492        with negative dividends on many machines */
13493     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13494
13495     if (second < 0) {
13496         sign = "-";
13497         second = -second;
13498     }
13499     
13500     day = second / (60 * 60 * 24);
13501     second = second % (60 * 60 * 24);
13502     hour = second / (60 * 60);
13503     second = second % (60 * 60);
13504     minute = second / 60;
13505     second = second % 60;
13506     
13507     if (day > 0)
13508       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13509               sign, day, hour, minute, second);
13510     else if (hour > 0)
13511       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13512     else
13513       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13514     
13515     return buf;
13516 }
13517
13518
13519 /*
13520  * This is necessary because some C libraries aren't ANSI C compliant yet.
13521  */
13522 char *
13523 StrStr(string, match)
13524      char *string, *match;
13525 {
13526     int i, length;
13527     
13528     length = strlen(match);
13529     
13530     for (i = strlen(string) - length; i >= 0; i--, string++)
13531       if (!strncmp(match, string, length))
13532         return string;
13533     
13534     return NULL;
13535 }
13536
13537 char *
13538 StrCaseStr(string, match)
13539      char *string, *match;
13540 {
13541     int i, j, length;
13542     
13543     length = strlen(match);
13544     
13545     for (i = strlen(string) - length; i >= 0; i--, string++) {
13546         for (j = 0; j < length; j++) {
13547             if (ToLower(match[j]) != ToLower(string[j]))
13548               break;
13549         }
13550         if (j == length) return string;
13551     }
13552
13553     return NULL;
13554 }
13555
13556 #ifndef _amigados
13557 int
13558 StrCaseCmp(s1, s2)
13559      char *s1, *s2;
13560 {
13561     char c1, c2;
13562     
13563     for (;;) {
13564         c1 = ToLower(*s1++);
13565         c2 = ToLower(*s2++);
13566         if (c1 > c2) return 1;
13567         if (c1 < c2) return -1;
13568         if (c1 == NULLCHAR) return 0;
13569     }
13570 }
13571
13572
13573 int
13574 ToLower(c)
13575      int c;
13576 {
13577     return isupper(c) ? tolower(c) : c;
13578 }
13579
13580
13581 int
13582 ToUpper(c)
13583      int c;
13584 {
13585     return islower(c) ? toupper(c) : c;
13586 }
13587 #endif /* !_amigados    */
13588
13589 char *
13590 StrSave(s)
13591      char *s;
13592 {
13593     char *ret;
13594
13595     if ((ret = (char *) malloc(strlen(s) + 1))) {
13596         strcpy(ret, s);
13597     }
13598     return ret;
13599 }
13600
13601 char *
13602 StrSavePtr(s, savePtr)
13603      char *s, **savePtr;
13604 {
13605     if (*savePtr) {
13606         free(*savePtr);
13607     }
13608     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13609         strcpy(*savePtr, s);
13610     }
13611     return(*savePtr);
13612 }
13613
13614 char *
13615 PGNDate()
13616 {
13617     time_t clock;
13618     struct tm *tm;
13619     char buf[MSG_SIZ];
13620
13621     clock = time((time_t *)NULL);
13622     tm = localtime(&clock);
13623     sprintf(buf, "%04d.%02d.%02d",
13624             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13625     return StrSave(buf);
13626 }
13627
13628
13629 char *
13630 PositionToFEN(move, overrideCastling)
13631      int move;
13632      char *overrideCastling;
13633 {
13634     int i, j, fromX, fromY, toX, toY;
13635     int whiteToPlay;
13636     char buf[128];
13637     char *p, *q;
13638     int emptycount;
13639     ChessSquare piece;
13640
13641     whiteToPlay = (gameMode == EditPosition) ?
13642       !blackPlaysFirst : (move % 2 == 0);
13643     p = buf;
13644
13645     /* Piece placement data */
13646     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13647         emptycount = 0;
13648         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13649             if (boards[move][i][j] == EmptySquare) {
13650                 emptycount++;
13651             } else { ChessSquare piece = boards[move][i][j];
13652                 if (emptycount > 0) {
13653                     if(emptycount<10) /* [HGM] can be >= 10 */
13654                         *p++ = '0' + emptycount;
13655                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13656                     emptycount = 0;
13657                 }
13658                 if(PieceToChar(piece) == '+') {
13659                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13660                     *p++ = '+';
13661                     piece = (ChessSquare)(DEMOTED piece);
13662                 } 
13663                 *p++ = PieceToChar(piece);
13664                 if(p[-1] == '~') {
13665                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13666                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13667                     *p++ = '~';
13668                 }
13669             }
13670         }
13671         if (emptycount > 0) {
13672             if(emptycount<10) /* [HGM] can be >= 10 */
13673                 *p++ = '0' + emptycount;
13674             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13675             emptycount = 0;
13676         }
13677         *p++ = '/';
13678     }
13679     *(p - 1) = ' ';
13680
13681     /* [HGM] print Crazyhouse or Shogi holdings */
13682     if( gameInfo.holdingsWidth ) {
13683         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13684         q = p;
13685         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13686             piece = boards[move][i][BOARD_WIDTH-1];
13687             if( piece != EmptySquare )
13688               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13689                   *p++ = PieceToChar(piece);
13690         }
13691         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13692             piece = boards[move][BOARD_HEIGHT-i-1][0];
13693             if( piece != EmptySquare )
13694               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13695                   *p++ = PieceToChar(piece);
13696         }
13697
13698         if( q == p ) *p++ = '-';
13699         *p++ = ']';
13700         *p++ = ' ';
13701     }
13702
13703     /* Active color */
13704     *p++ = whiteToPlay ? 'w' : 'b';
13705     *p++ = ' ';
13706
13707   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13708     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13709   } else {
13710   if(nrCastlingRights) {
13711      q = p;
13712      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13713        /* [HGM] write directly from rights */
13714            if(boards[move][CASTLING][2] != NoRights &&
13715               boards[move][CASTLING][0] != NoRights   )
13716                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13717            if(boards[move][CASTLING][2] != NoRights &&
13718               boards[move][CASTLING][1] != NoRights   )
13719                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13720            if(boards[move][CASTLING][5] != NoRights &&
13721               boards[move][CASTLING][3] != NoRights   )
13722                 *p++ = boards[move][CASTLING][3] + AAA;
13723            if(boards[move][CASTLING][5] != NoRights &&
13724               boards[move][CASTLING][4] != NoRights   )
13725                 *p++ = boards[move][CASTLING][4] + AAA;
13726      } else {
13727
13728         /* [HGM] write true castling rights */
13729         if( nrCastlingRights == 6 ) {
13730             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13731                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13732             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13733                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13734             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13735                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13736             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13737                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13738         }
13739      }
13740      if (q == p) *p++ = '-'; /* No castling rights */
13741      *p++ = ' ';
13742   }
13743
13744   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13745      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13746     /* En passant target square */
13747     if (move > backwardMostMove) {
13748         fromX = moveList[move - 1][0] - AAA;
13749         fromY = moveList[move - 1][1] - ONE;
13750         toX = moveList[move - 1][2] - AAA;
13751         toY = moveList[move - 1][3] - ONE;
13752         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13753             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13754             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13755             fromX == toX) {
13756             /* 2-square pawn move just happened */
13757             *p++ = toX + AAA;
13758             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13759         } else {
13760             *p++ = '-';
13761         }
13762     } else if(move == backwardMostMove) {
13763         // [HGM] perhaps we should always do it like this, and forget the above?
13764         if((signed char)boards[move][EP_STATUS] >= 0) {
13765             *p++ = boards[move][EP_STATUS] + AAA;
13766             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13767         } else {
13768             *p++ = '-';
13769         }
13770     } else {
13771         *p++ = '-';
13772     }
13773     *p++ = ' ';
13774   }
13775   }
13776
13777     /* [HGM] find reversible plies */
13778     {   int i = 0, j=move;
13779
13780         if (appData.debugMode) { int k;
13781             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13782             for(k=backwardMostMove; k<=forwardMostMove; k++)
13783                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13784
13785         }
13786
13787         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13788         if( j == backwardMostMove ) i += initialRulePlies;
13789         sprintf(p, "%d ", i);
13790         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13791     }
13792     /* Fullmove number */
13793     sprintf(p, "%d", (move / 2) + 1);
13794     
13795     return StrSave(buf);
13796 }
13797
13798 Boolean
13799 ParseFEN(board, blackPlaysFirst, fen)
13800     Board board;
13801      int *blackPlaysFirst;
13802      char *fen;
13803 {
13804     int i, j;
13805     char *p;
13806     int emptycount;
13807     ChessSquare piece;
13808
13809     p = fen;
13810
13811     /* [HGM] by default clear Crazyhouse holdings, if present */
13812     if(gameInfo.holdingsWidth) {
13813        for(i=0; i<BOARD_HEIGHT; i++) {
13814            board[i][0]             = EmptySquare; /* black holdings */
13815            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13816            board[i][1]             = (ChessSquare) 0; /* black counts */
13817            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13818        }
13819     }
13820
13821     /* Piece placement data */
13822     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13823         j = 0;
13824         for (;;) {
13825             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13826                 if (*p == '/') p++;
13827                 emptycount = gameInfo.boardWidth - j;
13828                 while (emptycount--)
13829                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13830                 break;
13831 #if(BOARD_FILES >= 10)
13832             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13833                 p++; emptycount=10;
13834                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13835                 while (emptycount--)
13836                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13837 #endif
13838             } else if (isdigit(*p)) {
13839                 emptycount = *p++ - '0';
13840                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13841                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13842                 while (emptycount--)
13843                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13844             } else if (*p == '+' || isalpha(*p)) {
13845                 if (j >= gameInfo.boardWidth) return FALSE;
13846                 if(*p=='+') {
13847                     piece = CharToPiece(*++p);
13848                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13849                     piece = (ChessSquare) (PROMOTED piece ); p++;
13850                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13851                 } else piece = CharToPiece(*p++);
13852
13853                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13854                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13855                     piece = (ChessSquare) (PROMOTED piece);
13856                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13857                     p++;
13858                 }
13859                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13860             } else {
13861                 return FALSE;
13862             }
13863         }
13864     }
13865     while (*p == '/' || *p == ' ') p++;
13866
13867     /* [HGM] look for Crazyhouse holdings here */
13868     while(*p==' ') p++;
13869     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13870         if(*p == '[') p++;
13871         if(*p == '-' ) *p++; /* empty holdings */ else {
13872             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13873             /* if we would allow FEN reading to set board size, we would   */
13874             /* have to add holdings and shift the board read so far here   */
13875             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13876                 *p++;
13877                 if((int) piece >= (int) BlackPawn ) {
13878                     i = (int)piece - (int)BlackPawn;
13879                     i = PieceToNumber((ChessSquare)i);
13880                     if( i >= gameInfo.holdingsSize ) return FALSE;
13881                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13882                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13883                 } else {
13884                     i = (int)piece - (int)WhitePawn;
13885                     i = PieceToNumber((ChessSquare)i);
13886                     if( i >= gameInfo.holdingsSize ) return FALSE;
13887                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13888                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13889                 }
13890             }
13891         }
13892         if(*p == ']') *p++;
13893     }
13894
13895     while(*p == ' ') p++;
13896
13897     /* Active color */
13898     switch (*p++) {
13899       case 'w':
13900         *blackPlaysFirst = FALSE;
13901         break;
13902       case 'b': 
13903         *blackPlaysFirst = TRUE;
13904         break;
13905       default:
13906         return FALSE;
13907     }
13908
13909     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13910     /* return the extra info in global variiables             */
13911
13912     /* set defaults in case FEN is incomplete */
13913     board[EP_STATUS] = EP_UNKNOWN;
13914     for(i=0; i<nrCastlingRights; i++ ) {
13915         board[CASTLING][i] =
13916             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13917     }   /* assume possible unless obviously impossible */
13918     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13919     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13920     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13921     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13922     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13923     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13924     FENrulePlies = 0;
13925
13926     while(*p==' ') p++;
13927     if(nrCastlingRights) {
13928       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13929           /* castling indicator present, so default becomes no castlings */
13930           for(i=0; i<nrCastlingRights; i++ ) {
13931                  board[CASTLING][i] = NoRights;
13932           }
13933       }
13934       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13935              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13936              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13937              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13938         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13939
13940         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13941             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13942             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13943         }
13944         switch(c) {
13945           case'K':
13946               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13947               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13948               board[CASTLING][2] = whiteKingFile;
13949               break;
13950           case'Q':
13951               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13952               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13953               board[CASTLING][2] = whiteKingFile;
13954               break;
13955           case'k':
13956               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13957               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13958               board[CASTLING][5] = blackKingFile;
13959               break;
13960           case'q':
13961               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13962               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13963               board[CASTLING][5] = blackKingFile;
13964           case '-':
13965               break;
13966           default: /* FRC castlings */
13967               if(c >= 'a') { /* black rights */
13968                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13969                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13970                   if(i == BOARD_RGHT) break;
13971                   board[CASTLING][5] = i;
13972                   c -= AAA;
13973                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13974                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13975                   if(c > i)
13976                       board[CASTLING][3] = c;
13977                   else
13978                       board[CASTLING][4] = c;
13979               } else { /* white rights */
13980                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13981                     if(board[0][i] == WhiteKing) break;
13982                   if(i == BOARD_RGHT) break;
13983                   board[CASTLING][2] = i;
13984                   c -= AAA - 'a' + 'A';
13985                   if(board[0][c] >= WhiteKing) break;
13986                   if(c > i)
13987                       board[CASTLING][0] = c;
13988                   else
13989                       board[CASTLING][1] = c;
13990               }
13991         }
13992       }
13993     if (appData.debugMode) {
13994         fprintf(debugFP, "FEN castling rights:");
13995         for(i=0; i<nrCastlingRights; i++)
13996         fprintf(debugFP, " %d", board[CASTLING][i]);
13997         fprintf(debugFP, "\n");
13998     }
13999
14000       while(*p==' ') p++;
14001     }
14002
14003     /* read e.p. field in games that know e.p. capture */
14004     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14005        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14006       if(*p=='-') {
14007         p++; board[EP_STATUS] = EP_NONE;
14008       } else {
14009          char c = *p++ - AAA;
14010
14011          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14012          if(*p >= '0' && *p <='9') *p++;
14013          board[EP_STATUS] = c;
14014       }
14015     }
14016
14017
14018     if(sscanf(p, "%d", &i) == 1) {
14019         FENrulePlies = i; /* 50-move ply counter */
14020         /* (The move number is still ignored)    */
14021     }
14022
14023     return TRUE;
14024 }
14025       
14026 void
14027 EditPositionPasteFEN(char *fen)
14028 {
14029   if (fen != NULL) {
14030     Board initial_position;
14031
14032     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14033       DisplayError(_("Bad FEN position in clipboard"), 0);
14034       return ;
14035     } else {
14036       int savedBlackPlaysFirst = blackPlaysFirst;
14037       EditPositionEvent();
14038       blackPlaysFirst = savedBlackPlaysFirst;
14039       CopyBoard(boards[0], initial_position);
14040       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14041       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14042       DisplayBothClocks();
14043       DrawPosition(FALSE, boards[currentMove]);
14044     }
14045   }
14046 }
14047
14048 static char cseq[12] = "\\   ";
14049
14050 Boolean set_cont_sequence(char *new_seq)
14051 {
14052     int len;
14053     Boolean ret;
14054
14055     // handle bad attempts to set the sequence
14056         if (!new_seq)
14057                 return 0; // acceptable error - no debug
14058
14059     len = strlen(new_seq);
14060     ret = (len > 0) && (len < sizeof(cseq));
14061     if (ret)
14062         strcpy(cseq, new_seq);
14063     else if (appData.debugMode)
14064         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14065     return ret;
14066 }
14067
14068 /*
14069     reformat a source message so words don't cross the width boundary.  internal
14070     newlines are not removed.  returns the wrapped size (no null character unless
14071     included in source message).  If dest is NULL, only calculate the size required
14072     for the dest buffer.  lp argument indicats line position upon entry, and it's
14073     passed back upon exit.
14074 */
14075 int wrap(char *dest, char *src, int count, int width, int *lp)
14076 {
14077     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14078
14079     cseq_len = strlen(cseq);
14080     old_line = line = *lp;
14081     ansi = len = clen = 0;
14082
14083     for (i=0; i < count; i++)
14084     {
14085         if (src[i] == '\033')
14086             ansi = 1;
14087
14088         // if we hit the width, back up
14089         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14090         {
14091             // store i & len in case the word is too long
14092             old_i = i, old_len = len;
14093
14094             // find the end of the last word
14095             while (i && src[i] != ' ' && src[i] != '\n')
14096             {
14097                 i--;
14098                 len--;
14099             }
14100
14101             // word too long?  restore i & len before splitting it
14102             if ((old_i-i+clen) >= width)
14103             {
14104                 i = old_i;
14105                 len = old_len;
14106             }
14107
14108             // extra space?
14109             if (i && src[i-1] == ' ')
14110                 len--;
14111
14112             if (src[i] != ' ' && src[i] != '\n')
14113             {
14114                 i--;
14115                 if (len)
14116                     len--;
14117             }
14118
14119             // now append the newline and continuation sequence
14120             if (dest)
14121                 dest[len] = '\n';
14122             len++;
14123             if (dest)
14124                 strncpy(dest+len, cseq, cseq_len);
14125             len += cseq_len;
14126             line = cseq_len;
14127             clen = cseq_len;
14128             continue;
14129         }
14130
14131         if (dest)
14132             dest[len] = src[i];
14133         len++;
14134         if (!ansi)
14135             line++;
14136         if (src[i] == '\n')
14137             line = 0;
14138         if (src[i] == 'm')
14139             ansi = 0;
14140     }
14141     if (dest && appData.debugMode)
14142     {
14143         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14144             count, width, line, len, *lp);
14145         show_bytes(debugFP, src, count);
14146         fprintf(debugFP, "\ndest: ");
14147         show_bytes(debugFP, dest, len);
14148         fprintf(debugFP, "\n");
14149     }
14150     *lp = dest ? line : old_line;
14151
14152     return len;
14153 }
14154
14155 // [HGM] vari: routines for shelving variations
14156
14157 void 
14158 PushTail(int firstMove, int lastMove)
14159 {
14160         int i, j, nrMoves = lastMove - firstMove;
14161
14162         if(appData.icsActive) { // only in local mode
14163                 forwardMostMove = currentMove; // mimic old ICS behavior
14164                 return;
14165         }
14166         if(storedGames >= MAX_VARIATIONS-1) return;
14167
14168         // push current tail of game on stack
14169         savedResult[storedGames] = gameInfo.result;
14170         savedDetails[storedGames] = gameInfo.resultDetails;
14171         gameInfo.resultDetails = NULL;
14172         savedFirst[storedGames] = firstMove;
14173         savedLast [storedGames] = lastMove;
14174         savedFramePtr[storedGames] = framePtr;
14175         framePtr -= nrMoves; // reserve space for the boards
14176         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14177             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14178             for(j=0; j<MOVE_LEN; j++)
14179                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14180             for(j=0; j<2*MOVE_LEN; j++)
14181                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14182             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14183             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14184             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14185             pvInfoList[firstMove+i-1].depth = 0;
14186             commentList[framePtr+i] = commentList[firstMove+i];
14187             commentList[firstMove+i] = NULL;
14188         }
14189
14190         storedGames++;
14191         forwardMostMove = currentMove; // truncte game so we can start variation
14192         if(storedGames == 1) GreyRevert(FALSE);
14193 }
14194
14195 Boolean
14196 PopTail(Boolean annotate)
14197 {
14198         int i, j, nrMoves;
14199         char buf[8000], moveBuf[20];
14200
14201         if(appData.icsActive) return FALSE; // only in local mode
14202         if(!storedGames) return FALSE; // sanity
14203
14204         storedGames--;
14205         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14206         nrMoves = savedLast[storedGames] - currentMove;
14207         if(annotate) {
14208                 int cnt = 10;
14209                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14210                 else strcpy(buf, "(");
14211                 for(i=currentMove; i<forwardMostMove; i++) {
14212                         if(WhiteOnMove(i))
14213                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14214                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14215                         strcat(buf, moveBuf);
14216                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14217                 }
14218                 strcat(buf, ")");
14219         }
14220         for(i=1; i<nrMoves; i++) { // copy last variation back
14221             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14222             for(j=0; j<MOVE_LEN; j++)
14223                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14224             for(j=0; j<2*MOVE_LEN; j++)
14225                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14226             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14227             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14228             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14229             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14230             commentList[currentMove+i] = commentList[framePtr+i];
14231             commentList[framePtr+i] = NULL;
14232         }
14233         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14234         framePtr = savedFramePtr[storedGames];
14235         gameInfo.result = savedResult[storedGames];
14236         if(gameInfo.resultDetails != NULL) {
14237             free(gameInfo.resultDetails);
14238       }
14239         gameInfo.resultDetails = savedDetails[storedGames];
14240         forwardMostMove = currentMove + nrMoves;
14241         if(storedGames == 0) GreyRevert(TRUE);
14242         return TRUE;
14243 }
14244
14245 void 
14246 CleanupTail()
14247 {       // remove all shelved variations
14248         int i;
14249         for(i=0; i<storedGames; i++) {
14250             if(savedDetails[i])
14251                 free(savedDetails[i]);
14252             savedDetails[i] = NULL;
14253         }
14254         for(i=framePtr; i<MAX_MOVES; i++) {
14255                 if(commentList[i]) free(commentList[i]);
14256                 commentList[i] = NULL;
14257         }
14258         framePtr = MAX_MOVES-1;
14259         storedGames = 0;
14260 }