a24a0f87de347f89b5baa4e30526ea28b6529787
[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, 2010 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, char *castle, char *ep));
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 KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
242 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
246 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252
253 /* States for ics_getting_history */
254 #define H_FALSE 0
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
260
261 /* whosays values for GameEnds */
262 #define GE_ICS 0
263 #define GE_ENGINE 1
264 #define GE_PLAYER 2
265 #define GE_FILE 3
266 #define GE_XBOARD 4
267 #define GE_ENGINE1 5
268 #define GE_ENGINE2 6
269
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
272
273 /* Different types of move when calling RegisterMove */
274 #define CMAIL_MOVE   0
275 #define CMAIL_RESIGN 1
276 #define CMAIL_DRAW   2
277 #define CMAIL_ACCEPT 3
278
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
283
284 /* Telnet protocol constants */
285 #define TN_WILL 0373
286 #define TN_WONT 0374
287 #define TN_DO   0375
288 #define TN_DONT 0376
289 #define TN_IAC  0377
290 #define TN_ECHO 0001
291 #define TN_SGA  0003
292 #define TN_PORT 23
293
294 /* [AS] */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
296 {
297     assert( dst != NULL );
298     assert( src != NULL );
299     assert( count > 0 );
300
301     strncpy( dst, src, count );
302     dst[ count-1 ] = '\0';
303     return dst;
304 }
305
306 /* Some compiler can't cast u64 to double
307  * This function do the job for us:
308
309  * We use the highest bit for cast, this only
310  * works if the highest bit is not
311  * in use (This should not happen)
312  *
313  * We used this for all compiler
314  */
315 double
316 u64ToDouble(u64 value)
317 {
318   double r;
319   u64 tmp = value & u64Const(0x7fffffffffffffff);
320   r = (double)(s64)tmp;
321   if (value & u64Const(0x8000000000000000))
322        r +=  9.2233720368547758080e18; /* 2^63 */
323  return r;
324 }
325
326 /* Fake up flags for now, as we aren't keeping track of castling
327    availability yet. [HGM] Change of logic: the flag now only
328    indicates the type of castlings allowed by the rule of the game.
329    The actual rights themselves are maintained in the array
330    castlingRights, as part of the game history, and are not probed
331    by this function.
332  */
333 int
334 PosFlags(index)
335 {
336   int flags = F_ALL_CASTLE_OK;
337   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338   switch (gameInfo.variant) {
339   case VariantSuicide:
340     flags &= ~F_ALL_CASTLE_OK;
341   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342     flags |= F_IGNORE_CHECK;
343   case VariantLosers:
344     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345     break;
346   case VariantAtomic:
347     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
348     break;
349   case VariantKriegspiel:
350     flags |= F_KRIEGSPIEL_CAPTURE;
351     break;
352   case VariantCapaRandom: 
353   case VariantFischeRandom:
354     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355   case VariantNoCastle:
356   case VariantShatranj:
357   case VariantCourier:
358     flags &= ~F_ALL_CASTLE_OK;
359     break;
360   default:
361     break;
362   }
363   return flags;
364 }
365
366 FILE *gameFileFP, *debugFP;
367
368 /* 
369     [AS] Note: sometimes, the sscanf() function is used to parse the input
370     into a fixed-size buffer. Because of this, we must be prepared to
371     receive strings as long as the size of the input buffer, which is currently
372     set to 4K for Windows and 8K for the rest.
373     So, we must either allocate sufficiently large buffers here, or
374     reduce the size of the input buffer in the input reading part.
375 */
376
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
379 char thinkOutput1[MSG_SIZ*10];
380
381 ChessProgramState first, second;
382
383 /* premove variables */
384 int premoveToX = 0;
385 int premoveToY = 0;
386 int premoveFromX = 0;
387 int premoveFromY = 0;
388 int premovePromoChar = 0;
389 int gotPremove = 0;
390 Boolean alarmSounded;
391 /* end premove variables */
392
393 char *ics_prefix = "$";
394 int ics_type = ICS_GENERIC;
395
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
397 int pauseExamForwardMostMove = 0;
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
404 int whiteFlag = FALSE, blackFlag = FALSE;
405 int userOfferedDraw = FALSE;
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
408 int cmailMoveType[CMAIL_MAX_GAMES];
409 long ics_clock_paused = 0;
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
412 GameMode gameMode = BeginningOfGame;
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
416 int hiddenThinkOutputState = 0; /* [AS] */
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
418 int adjudicateLossPlies = 6;
419 char white_holding[64], black_holding[64];
420 TimeMark lastNodeCountTime;
421 long lastNodeCount=0;
422 int have_sent_ICS_logon = 0;
423 int movesPerSession;
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
425 long timeControl_2; /* [AS] Allow separate time controls */
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
427 long timeRemaining[2][MAX_MOVES];
428 int matchGame = 0;
429 TimeMark programStartTime;
430 char ics_handle[MSG_SIZ];
431 int have_set_title = 0;
432
433 /* animateTraining preserves the state of appData.animate
434  * when Training mode is activated. This allows the
435  * response to be animated when appData.animate == TRUE and
436  * appData.animateDragging == TRUE.
437  */
438 Boolean animateTraining;
439
440 GameInfo gameInfo;
441
442 AppData appData;
443
444 Board boards[MAX_MOVES];
445 /* [HGM] Following 7 needed for accurate legality tests: */
446 signed char  epStatus[MAX_MOVES];
447 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
448 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
449 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
450 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
451 int   initialRulePlies, FENrulePlies;
452 char  FENepStatus;
453 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int loadFlag = 0; 
455 int shuffleOpenings;
456 int mute; // mute all sounds
457
458 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
459     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
460         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
461     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
462         BlackKing, BlackBishop, BlackKnight, BlackRook }
463 };
464
465 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
466     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
467         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
468     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
469         BlackKing, BlackKing, BlackKnight, BlackRook }
470 };
471
472 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
473     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
474         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
475     { BlackRook, BlackMan, BlackBishop, BlackQueen,
476         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 };
478
479 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
480     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
481         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
482     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
483         BlackKing, BlackBishop, BlackKnight, BlackRook }
484 };
485
486 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
487     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
488         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
489     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
490         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
491 };
492
493
494 #if (BOARD_SIZE>=10)
495 ChessSquare ShogiArray[2][BOARD_SIZE] = {
496     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
497         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
498     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
499         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 };
501
502 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
504         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
506         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
510     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
511         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
513         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 };
515
516 ChessSquare GreatArray[2][BOARD_SIZE] = {
517     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
518         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
519     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
520         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 };
522
523 ChessSquare JanusArray[2][BOARD_SIZE] = {
524     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
525         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
526     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
527         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
528 };
529
530 #ifdef GOTHIC
531 ChessSquare GothicArray[2][BOARD_SIZE] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
533         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
535         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 };
537 #else // !GOTHIC
538 #define GothicArray CapablancaArray
539 #endif // !GOTHIC
540
541 #ifdef FALCON
542 ChessSquare FalconArray[2][BOARD_SIZE] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
544         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
546         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 };
548 #else // !FALCON
549 #define FalconArray CapablancaArray
550 #endif // !FALCON
551
552 #else // !(BOARD_SIZE>=10)
553 #define XiangqiPosition FIDEArray
554 #define CapablancaArray FIDEArray
555 #define GothicArray FIDEArray
556 #define GreatArray FIDEArray
557 #endif // !(BOARD_SIZE>=10)
558
559 #if (BOARD_SIZE>=12)
560 ChessSquare CourierArray[2][BOARD_SIZE] = {
561     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
562         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
563     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
564         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
565 };
566 #else // !(BOARD_SIZE>=12)
567 #define CourierArray CapablancaArray
568 #endif // !(BOARD_SIZE>=12)
569
570
571 Board initialPosition;
572
573
574 /* Convert str to a rating. Checks for special cases of "----",
575
576    "++++", etc. Also strips ()'s */
577 int
578 string_to_rating(str)
579   char *str;
580 {
581   while(*str && !isdigit(*str)) ++str;
582   if (!*str)
583     return 0;   /* One of the special "no rating" cases */
584   else
585     return atoi(str);
586 }
587
588 void
589 ClearProgramStats()
590 {
591     /* Init programStats */
592     programStats.movelist[0] = 0;
593     programStats.depth = 0;
594     programStats.nr_moves = 0;
595     programStats.moves_left = 0;
596     programStats.nodes = 0;
597     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
598     programStats.score = 0;
599     programStats.got_only_move = 0;
600     programStats.got_fail = 0;
601     programStats.line_is_book = 0;
602 }
603
604 void
605 InitBackEnd1()
606 {
607     int matched, min, sec;
608
609     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
610
611     GetTimeMark(&programStartTime);
612     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
613
614     ClearProgramStats();
615     programStats.ok_to_send = 1;
616     programStats.seen_stat = 0;
617
618     /*
619      * Initialize game list
620      */
621     ListNew(&gameList);
622
623
624     /*
625      * Internet chess server status
626      */
627     if (appData.icsActive) {
628         appData.matchMode = FALSE;
629         appData.matchGames = 0;
630 #if ZIPPY       
631         appData.noChessProgram = !appData.zippyPlay;
632 #else
633         appData.zippyPlay = FALSE;
634         appData.zippyTalk = FALSE;
635         appData.noChessProgram = TRUE;
636 #endif
637         if (*appData.icsHelper != NULLCHAR) {
638             appData.useTelnet = TRUE;
639             appData.telnetProgram = appData.icsHelper;
640         }
641     } else {
642         appData.zippyTalk = appData.zippyPlay = FALSE;
643     }
644
645     /* [AS] Initialize pv info list [HGM] and game state */
646     {
647         int i, j;
648
649         for( i=0; i<MAX_MOVES; i++ ) {
650             pvInfoList[i].depth = -1;
651             epStatus[i]=EP_NONE;
652             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
653         }
654     }
655
656     /*
657      * Parse timeControl resource
658      */
659     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
660                           appData.movesPerSession)) {
661         char buf[MSG_SIZ];
662         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
663         DisplayFatalError(buf, 0, 2);
664     }
665
666     /*
667      * Parse searchTime resource
668      */
669     if (*appData.searchTime != NULLCHAR) {
670         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
671         if (matched == 1) {
672             searchTime = min * 60;
673         } else if (matched == 2) {
674             searchTime = min * 60 + sec;
675         } else {
676             char buf[MSG_SIZ];
677             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
678             DisplayFatalError(buf, 0, 2);
679         }
680     }
681
682     /* [AS] Adjudication threshold */
683     adjudicateLossThreshold = appData.adjudicateLossThreshold;
684     
685     first.which = "first";
686     second.which = "second";
687     first.maybeThinking = second.maybeThinking = FALSE;
688     first.pr = second.pr = NoProc;
689     first.isr = second.isr = NULL;
690     first.sendTime = second.sendTime = 2;
691     first.sendDrawOffers = 1;
692     if (appData.firstPlaysBlack) {
693         first.twoMachinesColor = "black\n";
694         second.twoMachinesColor = "white\n";
695     } else {
696         first.twoMachinesColor = "white\n";
697         second.twoMachinesColor = "black\n";
698     }
699     first.program = appData.firstChessProgram;
700     second.program = appData.secondChessProgram;
701     first.host = appData.firstHost;
702     second.host = appData.secondHost;
703     first.dir = appData.firstDirectory;
704     second.dir = appData.secondDirectory;
705     first.other = &second;
706     second.other = &first;
707     first.initString = appData.initString;
708     second.initString = appData.secondInitString;
709     first.computerString = appData.firstComputerString;
710     second.computerString = appData.secondComputerString;
711     first.useSigint = second.useSigint = TRUE;
712     first.useSigterm = second.useSigterm = TRUE;
713     first.reuse = appData.reuseFirst;
714     second.reuse = appData.reuseSecond;
715     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
716     second.nps = appData.secondNPS;
717     first.useSetboard = second.useSetboard = FALSE;
718     first.useSAN = second.useSAN = FALSE;
719     first.usePing = second.usePing = FALSE;
720     first.lastPing = second.lastPing = 0;
721     first.lastPong = second.lastPong = 0;
722     first.usePlayother = second.usePlayother = FALSE;
723     first.useColors = second.useColors = TRUE;
724     first.useUsermove = second.useUsermove = FALSE;
725     first.sendICS = second.sendICS = FALSE;
726     first.sendName = second.sendName = appData.icsActive;
727     first.sdKludge = second.sdKludge = FALSE;
728     first.stKludge = second.stKludge = FALSE;
729     TidyProgramName(first.program, first.host, first.tidy);
730     TidyProgramName(second.program, second.host, second.tidy);
731     first.matchWins = second.matchWins = 0;
732     strcpy(first.variants, appData.variant);
733     strcpy(second.variants, appData.variant);
734     first.analysisSupport = second.analysisSupport = 2; /* detect */
735     first.analyzing = second.analyzing = FALSE;
736     first.initDone = second.initDone = FALSE;
737
738     /* New features added by Tord: */
739     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
740     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
741     /* End of new features added by Tord. */
742     first.fenOverride  = appData.fenOverride1;
743     second.fenOverride = appData.fenOverride2;
744
745     /* [HGM] time odds: set factor for each machine */
746     first.timeOdds  = appData.firstTimeOdds;
747     second.timeOdds = appData.secondTimeOdds;
748     { float norm = 1;
749         if(appData.timeOddsMode) {
750             norm = first.timeOdds;
751             if(norm > second.timeOdds) norm = second.timeOdds;
752         }
753         first.timeOdds /= norm;
754         second.timeOdds /= norm;
755     }
756
757     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
758     first.accumulateTC = appData.firstAccumulateTC;
759     second.accumulateTC = appData.secondAccumulateTC;
760     first.maxNrOfSessions = second.maxNrOfSessions = 1;
761
762     /* [HGM] debug */
763     first.debug = second.debug = FALSE;
764     first.supportsNPS = second.supportsNPS = UNKNOWN;
765
766     /* [HGM] options */
767     first.optionSettings  = appData.firstOptions;
768     second.optionSettings = appData.secondOptions;
769
770     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
771     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
772     first.isUCI = appData.firstIsUCI; /* [AS] */
773     second.isUCI = appData.secondIsUCI; /* [AS] */
774     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
775     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
776
777     if (appData.firstProtocolVersion > PROTOVER ||
778         appData.firstProtocolVersion < 1) {
779       char buf[MSG_SIZ];
780       sprintf(buf, _("protocol version %d not supported"),
781               appData.firstProtocolVersion);
782       DisplayFatalError(buf, 0, 2);
783     } else {
784       first.protocolVersion = appData.firstProtocolVersion;
785     }
786
787     if (appData.secondProtocolVersion > PROTOVER ||
788         appData.secondProtocolVersion < 1) {
789       char buf[MSG_SIZ];
790       sprintf(buf, _("protocol version %d not supported"),
791               appData.secondProtocolVersion);
792       DisplayFatalError(buf, 0, 2);
793     } else {
794       second.protocolVersion = appData.secondProtocolVersion;
795     }
796
797     if (appData.icsActive) {
798         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
799     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
800         appData.clockMode = FALSE;
801         first.sendTime = second.sendTime = 0;
802     }
803     
804 #if ZIPPY
805     /* Override some settings from environment variables, for backward
806        compatibility.  Unfortunately it's not feasible to have the env
807        vars just set defaults, at least in xboard.  Ugh.
808     */
809     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
810       ZippyInit();
811     }
812 #endif
813     
814     if (appData.noChessProgram) {
815         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
816         sprintf(programVersion, "%s", PACKAGE_STRING);
817     } else {
818       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
819       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
820       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821     }
822
823     if (!appData.icsActive) {
824       char buf[MSG_SIZ];
825       /* Check for variants that are supported only in ICS mode,
826          or not at all.  Some that are accepted here nevertheless
827          have bugs; see comments below.
828       */
829       VariantClass variant = StringToVariant(appData.variant);
830       switch (variant) {
831       case VariantBughouse:     /* need four players and two boards */
832       case VariantKriegspiel:   /* need to hide pieces and move details */
833       /* case VariantFischeRandom: (Fabien: moved below) */
834         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
835         DisplayFatalError(buf, 0, 2);
836         return;
837
838       case VariantUnknown:
839       case VariantLoadable:
840       case Variant29:
841       case Variant30:
842       case Variant31:
843       case Variant32:
844       case Variant33:
845       case Variant34:
846       case Variant35:
847       case Variant36:
848       default:
849         sprintf(buf, _("Unknown variant name %s"), appData.variant);
850         DisplayFatalError(buf, 0, 2);
851         return;
852
853       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
854       case VariantFairy:      /* [HGM] TestLegality definitely off! */
855       case VariantGothic:     /* [HGM] should work */
856       case VariantCapablanca: /* [HGM] should work */
857       case VariantCourier:    /* [HGM] initial forced moves not implemented */
858       case VariantShogi:      /* [HGM] drops not tested for legality */
859       case VariantKnightmate: /* [HGM] should work */
860       case VariantCylinder:   /* [HGM] untested */
861       case VariantFalcon:     /* [HGM] untested */
862       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
863                                  offboard interposition not understood */
864       case VariantNormal:     /* definitely works! */
865       case VariantWildCastle: /* pieces not automatically shuffled */
866       case VariantNoCastle:   /* pieces not automatically shuffled */
867       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
868       case VariantLosers:     /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantSuicide:    /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantGiveaway:   /* should work except for win condition,
873                                  and doesn't know captures are mandatory */
874       case VariantTwoKings:   /* should work */
875       case VariantAtomic:     /* should work except for win condition */
876       case Variant3Check:     /* should work except for win condition */
877       case VariantShatranj:   /* should work except for all win conditions */
878       case VariantBerolina:   /* might work if TestLegality is off */
879       case VariantCapaRandom: /* should work */
880       case VariantJanus:      /* should work */
881       case VariantSuper:      /* experimental */
882       case VariantGreat:      /* experimental, requires legality testing to be off */
883         break;
884       }
885     }
886
887     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
888     InitEngineUCI( installDir, &second );
889 }
890
891 int NextIntegerFromString( char ** str, long * value )
892 {
893     int result = -1;
894     char * s = *str;
895
896     while( *s == ' ' || *s == '\t' ) {
897         s++;
898     }
899
900     *value = 0;
901
902     if( *s >= '0' && *s <= '9' ) {
903         while( *s >= '0' && *s <= '9' ) {
904             *value = *value * 10 + (*s - '0');
905             s++;
906         }
907
908         result = 0;
909     }
910
911     *str = s;
912
913     return result;
914 }
915
916 int NextTimeControlFromString( char ** str, long * value )
917 {
918     long temp;
919     int result = NextIntegerFromString( str, &temp );
920
921     if( result == 0 ) {
922         *value = temp * 60; /* Minutes */
923         if( **str == ':' ) {
924             (*str)++;
925             result = NextIntegerFromString( str, &temp );
926             *value += temp; /* Seconds */
927         }
928     }
929
930     return result;
931 }
932
933 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
934 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
935     int result = -1; long temp, temp2;
936
937     if(**str != '+') return -1; // old params remain in force!
938     (*str)++;
939     if( NextTimeControlFromString( str, &temp ) ) return -1;
940
941     if(**str != '/') {
942         /* time only: incremental or sudden-death time control */
943         if(**str == '+') { /* increment follows; read it */
944             (*str)++;
945             if(result = NextIntegerFromString( str, &temp2)) return -1;
946             *inc = temp2 * 1000;
947         } else *inc = 0;
948         *moves = 0; *tc = temp * 1000; 
949         return 0;
950     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
951
952     (*str)++; /* classical time control */
953     result = NextTimeControlFromString( str, &temp2);
954     if(result == 0) {
955         *moves = temp/60;
956         *tc    = temp2 * 1000;
957         *inc   = 0;
958     }
959     return result;
960 }
961
962 int GetTimeQuota(int movenr)
963 {   /* [HGM] get time to add from the multi-session time-control string */
964     int moves=1; /* kludge to force reading of first session */
965     long time, increment;
966     char *s = fullTimeControlString;
967
968     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
969     do {
970         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
971         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
972         if(movenr == -1) return time;    /* last move before new session     */
973         if(!moves) return increment;     /* current session is incremental   */
974         if(movenr >= 0) movenr -= moves; /* we already finished this session */
975     } while(movenr >= -1);               /* try again for next session       */
976
977     return 0; // no new time quota on this move
978 }
979
980 int
981 ParseTimeControl(tc, ti, mps)
982      char *tc;
983      int ti;
984      int mps;
985 {
986   long tc1;
987   long tc2;
988   char buf[MSG_SIZ];
989   
990   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991   if(ti > 0) {
992     if(mps)
993       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
994     else sprintf(buf, "+%s+%d", tc, ti);
995   } else {
996     if(mps)
997              sprintf(buf, "+%d/%s", mps, tc);
998     else sprintf(buf, "+%s", tc);
999   }
1000   fullTimeControlString = StrSave(buf);
1001   
1002   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1003     return FALSE;
1004   }
1005   
1006   if( *tc == '/' ) {
1007     /* Parse second time control */
1008     tc++;
1009     
1010     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1011       return FALSE;
1012     }
1013     
1014     if( tc2 == 0 ) {
1015       return FALSE;
1016     }
1017     
1018     timeControl_2 = tc2 * 1000;
1019   }
1020   else {
1021     timeControl_2 = 0;
1022   }
1023   
1024   if( tc1 == 0 ) {
1025     return FALSE;
1026   }
1027   
1028   timeControl = tc1 * 1000;
1029   
1030   if (ti >= 0) {
1031     timeIncrement = ti * 1000;  /* convert to ms */
1032     movesPerSession = 0;
1033   } else {
1034     timeIncrement = 0;
1035     movesPerSession = mps;
1036   }
1037   return TRUE;
1038 }
1039
1040 void
1041 InitBackEnd2()
1042 {
1043     if (appData.debugMode) {
1044         fprintf(debugFP, "%s\n", programVersion);
1045     }
1046
1047     set_cont_sequence(appData.wrapContSeq);
1048     if (appData.matchGames > 0) {
1049         appData.matchMode = TRUE;
1050     } else if (appData.matchMode) {
1051         appData.matchGames = 1;
1052     }
1053     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1054         appData.matchGames = appData.sameColorGames;
1055     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1056         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1057         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058     }
1059     Reset(TRUE, FALSE);
1060     if (appData.noChessProgram || first.protocolVersion == 1) {
1061       InitBackEnd3();
1062     } else {
1063       /* kludge: allow timeout for initial "feature" commands */
1064       FreezeUI();
1065       DisplayMessage("", _("Starting chess program"));
1066       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1067     }
1068 }
1069
1070 void
1071 InitBackEnd3 P((void))
1072 {
1073     GameMode initialMode;
1074     char buf[MSG_SIZ];
1075     int err;
1076
1077     InitChessProgram(&first, startedFromSetupPosition);
1078
1079
1080     if (appData.icsActive) {
1081 #ifdef WIN32
1082         /* [DM] Make a console window if needed [HGM] merged ifs */
1083         ConsoleCreate(); 
1084 #endif
1085         err = establish();
1086         if (err != 0) {
1087             if (*appData.icsCommPort != NULLCHAR) {
1088                 sprintf(buf, _("Could not open comm port %s"),  
1089                         appData.icsCommPort);
1090             } else {
1091                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1092                         appData.icsHost, appData.icsPort);
1093             }
1094             DisplayFatalError(buf, err, 1);
1095             return;
1096         }
1097         SetICSMode();
1098         telnetISR =
1099           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1100         fromUserISR =
1101           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1102         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1103             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1104     } else if (appData.noChessProgram) {
1105         SetNCPMode();
1106     } else {
1107         SetGNUMode();
1108     }
1109
1110     if (*appData.cmailGameName != NULLCHAR) {
1111         SetCmailMode();
1112         OpenLoopback(&cmailPR);
1113         cmailISR =
1114           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1115     }
1116     
1117     ThawUI();
1118     DisplayMessage("", "");
1119     if (StrCaseCmp(appData.initialMode, "") == 0) {
1120       initialMode = BeginningOfGame;
1121     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1122       initialMode = TwoMachinesPlay;
1123     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1124       initialMode = AnalyzeFile; 
1125     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1126       initialMode = AnalyzeMode;
1127     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1128       initialMode = MachinePlaysWhite;
1129     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1130       initialMode = MachinePlaysBlack;
1131     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1132       initialMode = EditGame;
1133     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1134       initialMode = EditPosition;
1135     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1136       initialMode = Training;
1137     } else {
1138       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1139       DisplayFatalError(buf, 0, 2);
1140       return;
1141     }
1142
1143     if (appData.matchMode) {
1144         /* Set up machine vs. machine match */
1145         if (appData.noChessProgram) {
1146             DisplayFatalError(_("Can't have a match with no chess programs"),
1147                               0, 2);
1148             return;
1149         }
1150         matchMode = TRUE;
1151         matchGame = 1;
1152         if (*appData.loadGameFile != NULLCHAR) {
1153             int index = appData.loadGameIndex; // [HGM] autoinc
1154             if(index<0) lastIndex = index = 1;
1155             if (!LoadGameFromFile(appData.loadGameFile,
1156                                   index,
1157                                   appData.loadGameFile, FALSE)) {
1158                 DisplayFatalError(_("Bad game file"), 0, 1);
1159                 return;
1160             }
1161         } else if (*appData.loadPositionFile != NULLCHAR) {
1162             int index = appData.loadPositionIndex; // [HGM] autoinc
1163             if(index<0) lastIndex = index = 1;
1164             if (!LoadPositionFromFile(appData.loadPositionFile,
1165                                       index,
1166                                       appData.loadPositionFile)) {
1167                 DisplayFatalError(_("Bad position file"), 0, 1);
1168                 return;
1169             }
1170         }
1171         TwoMachinesEvent();
1172     } else if (*appData.cmailGameName != NULLCHAR) {
1173         /* Set up cmail mode */
1174         ReloadCmailMsgEvent(TRUE);
1175     } else {
1176         /* Set up other modes */
1177         if (initialMode == AnalyzeFile) {
1178           if (*appData.loadGameFile == NULLCHAR) {
1179             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1180             return;
1181           }
1182         }
1183         if (*appData.loadGameFile != NULLCHAR) {
1184             (void) LoadGameFromFile(appData.loadGameFile,
1185                                     appData.loadGameIndex,
1186                                     appData.loadGameFile, TRUE);
1187         } else if (*appData.loadPositionFile != NULLCHAR) {
1188             (void) LoadPositionFromFile(appData.loadPositionFile,
1189                                         appData.loadPositionIndex,
1190                                         appData.loadPositionFile);
1191             /* [HGM] try to make self-starting even after FEN load */
1192             /* to allow automatic setup of fairy variants with wtm */
1193             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1194                 gameMode = BeginningOfGame;
1195                 setboardSpoiledMachineBlack = 1;
1196             }
1197             /* [HGM] loadPos: make that every new game uses the setup */
1198             /* from file as long as we do not switch variant          */
1199             if(!blackPlaysFirst) { int i;
1200                 startedFromPositionFile = TRUE;
1201                 CopyBoard(filePosition, boards[0]);
1202                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1203             }
1204         }
1205         if (initialMode == AnalyzeMode) {
1206           if (appData.noChessProgram) {
1207             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1208             return;
1209           }
1210           if (appData.icsActive) {
1211             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1212             return;
1213           }
1214           AnalyzeModeEvent();
1215         } else if (initialMode == AnalyzeFile) {
1216           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1217           ShowThinkingEvent();
1218           AnalyzeFileEvent();
1219           AnalysisPeriodicEvent(1);
1220         } else if (initialMode == MachinePlaysWhite) {
1221           if (appData.noChessProgram) {
1222             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1223                               0, 2);
1224             return;
1225           }
1226           if (appData.icsActive) {
1227             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1228                               0, 2);
1229             return;
1230           }
1231           MachineWhiteEvent();
1232         } else if (initialMode == MachinePlaysBlack) {
1233           if (appData.noChessProgram) {
1234             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1235                               0, 2);
1236             return;
1237           }
1238           if (appData.icsActive) {
1239             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1240                               0, 2);
1241             return;
1242           }
1243           MachineBlackEvent();
1244         } else if (initialMode == TwoMachinesPlay) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           TwoMachinesEvent();
1256         } else if (initialMode == EditGame) {
1257           EditGameEvent();
1258         } else if (initialMode == EditPosition) {
1259           EditPositionEvent();
1260         } else if (initialMode == Training) {
1261           if (*appData.loadGameFile == NULLCHAR) {
1262             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1263             return;
1264           }
1265           TrainingEvent();
1266         }
1267     }
1268 }
1269
1270 /*
1271  * Establish will establish a contact to a remote host.port.
1272  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1273  *  used to talk to the host.
1274  * Returns 0 if okay, error code if not.
1275  */
1276 int
1277 establish()
1278 {
1279     char buf[MSG_SIZ];
1280
1281     if (*appData.icsCommPort != NULLCHAR) {
1282         /* Talk to the host through a serial comm port */
1283         return OpenCommPort(appData.icsCommPort, &icsPR);
1284
1285     } else if (*appData.gateway != NULLCHAR) {
1286         if (*appData.remoteShell == NULLCHAR) {
1287             /* Use the rcmd protocol to run telnet program on a gateway host */
1288             snprintf(buf, sizeof(buf), "%s %s %s",
1289                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1290             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1291
1292         } else {
1293             /* Use the rsh program to run telnet program on a gateway host */
1294             if (*appData.remoteUser == NULLCHAR) {
1295                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1296                         appData.gateway, appData.telnetProgram,
1297                         appData.icsHost, appData.icsPort);
1298             } else {
1299                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1300                         appData.remoteShell, appData.gateway, 
1301                         appData.remoteUser, appData.telnetProgram,
1302                         appData.icsHost, appData.icsPort);
1303             }
1304             return StartChildProcess(buf, "", &icsPR);
1305
1306         }
1307     } else if (appData.useTelnet) {
1308         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1309
1310     } else {
1311         /* TCP socket interface differs somewhat between
1312            Unix and NT; handle details in the front end.
1313            */
1314         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315     }
1316 }
1317
1318 void
1319 show_bytes(fp, buf, count)
1320      FILE *fp;
1321      char *buf;
1322      int count;
1323 {
1324     while (count--) {
1325         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1326             fprintf(fp, "\\%03o", *buf & 0xff);
1327         } else {
1328             putc(*buf, fp);
1329         }
1330         buf++;
1331     }
1332     fflush(fp);
1333 }
1334
1335 /* Returns an errno value */
1336 int
1337 OutputMaybeTelnet(pr, message, count, outError)
1338      ProcRef pr;
1339      char *message;
1340      int count;
1341      int *outError;
1342 {
1343     char buf[8192], *p, *q, *buflim;
1344     int left, newcount, outcount;
1345
1346     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1347         *appData.gateway != NULLCHAR) {
1348         if (appData.debugMode) {
1349             fprintf(debugFP, ">ICS: ");
1350             show_bytes(debugFP, message, count);
1351             fprintf(debugFP, "\n");
1352         }
1353         return OutputToProcess(pr, message, count, outError);
1354     }
1355
1356     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1357     p = message;
1358     q = buf;
1359     left = count;
1360     newcount = 0;
1361     while (left) {
1362         if (q >= buflim) {
1363             if (appData.debugMode) {
1364                 fprintf(debugFP, ">ICS: ");
1365                 show_bytes(debugFP, buf, newcount);
1366                 fprintf(debugFP, "\n");
1367             }
1368             outcount = OutputToProcess(pr, buf, newcount, outError);
1369             if (outcount < newcount) return -1; /* to be sure */
1370             q = buf;
1371             newcount = 0;
1372         }
1373         if (*p == '\n') {
1374             *q++ = '\r';
1375             newcount++;
1376         } else if (((unsigned char) *p) == TN_IAC) {
1377             *q++ = (char) TN_IAC;
1378             newcount ++;
1379         }
1380         *q++ = *p++;
1381         newcount++;
1382         left--;
1383     }
1384     if (appData.debugMode) {
1385         fprintf(debugFP, ">ICS: ");
1386         show_bytes(debugFP, buf, newcount);
1387         fprintf(debugFP, "\n");
1388     }
1389     outcount = OutputToProcess(pr, buf, newcount, outError);
1390     if (outcount < newcount) return -1; /* to be sure */
1391     return count;
1392 }
1393
1394 void
1395 read_from_player(isr, closure, message, count, error)
1396      InputSourceRef isr;
1397      VOIDSTAR closure;
1398      char *message;
1399      int count;
1400      int error;
1401 {
1402     int outError, outCount;
1403     static int gotEof = 0;
1404
1405     /* Pass data read from player on to ICS */
1406     if (count > 0) {
1407         gotEof = 0;
1408         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1409         if (outCount < count) {
1410             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1411         }
1412     } else if (count < 0) {
1413         RemoveInputSource(isr);
1414         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1415     } else if (gotEof++ > 0) {
1416         RemoveInputSource(isr);
1417         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1418     }
1419 }
1420
1421 void
1422 KeepAlive()
1423 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1424     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1425     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1426     SendToICS("date\n");
1427     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1428 }
1429
1430 /* added routine for printf style output to ics */
1431 void ics_printf(char *format, ...)
1432 {
1433     char buffer[MSG_SIZ];
1434     va_list args;
1435
1436     va_start(args, format);
1437     vsnprintf(buffer, sizeof(buffer), format, args);
1438     buffer[sizeof(buffer)-1] = '\0';
1439     SendToICS(buffer);
1440     va_end(args);
1441 }
1442
1443 void
1444 SendToICS(s)
1445      char *s;
1446 {
1447     int count, outCount, outError;
1448
1449     if (icsPR == NULL) return;
1450
1451     count = strlen(s);
1452     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1453     if (outCount < count) {
1454         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1455     }
1456 }
1457
1458 /* This is used for sending logon scripts to the ICS. Sending
1459    without a delay causes problems when using timestamp on ICC
1460    (at least on my machine). */
1461 void
1462 SendToICSDelayed(s,msdelay)
1463      char *s;
1464      long msdelay;
1465 {
1466     int count, outCount, outError;
1467
1468     if (icsPR == NULL) return;
1469
1470     count = strlen(s);
1471     if (appData.debugMode) {
1472         fprintf(debugFP, ">ICS: ");
1473         show_bytes(debugFP, s, count);
1474         fprintf(debugFP, "\n");
1475     }
1476     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1477                                       msdelay);
1478     if (outCount < count) {
1479         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1480     }
1481 }
1482
1483
1484 /* Remove all highlighting escape sequences in s
1485    Also deletes any suffix starting with '(' 
1486    */
1487 char *
1488 StripHighlightAndTitle(s)
1489      char *s;
1490 {
1491     static char retbuf[MSG_SIZ];
1492     char *p = retbuf;
1493
1494     while (*s != NULLCHAR) {
1495         while (*s == '\033') {
1496             while (*s != NULLCHAR && !isalpha(*s)) s++;
1497             if (*s != NULLCHAR) s++;
1498         }
1499         while (*s != NULLCHAR && *s != '\033') {
1500             if (*s == '(' || *s == '[') {
1501                 *p = NULLCHAR;
1502                 return retbuf;
1503             }
1504             *p++ = *s++;
1505         }
1506     }
1507     *p = NULLCHAR;
1508     return retbuf;
1509 }
1510
1511 /* Remove all highlighting escape sequences in s */
1512 char *
1513 StripHighlight(s)
1514      char *s;
1515 {
1516     static char retbuf[MSG_SIZ];
1517     char *p = retbuf;
1518
1519     while (*s != NULLCHAR) {
1520         while (*s == '\033') {
1521             while (*s != NULLCHAR && !isalpha(*s)) s++;
1522             if (*s != NULLCHAR) s++;
1523         }
1524         while (*s != NULLCHAR && *s != '\033') {
1525             *p++ = *s++;
1526         }
1527     }
1528     *p = NULLCHAR;
1529     return retbuf;
1530 }
1531
1532 char *variantNames[] = VARIANT_NAMES;
1533 char *
1534 VariantName(v)
1535      VariantClass v;
1536 {
1537     return variantNames[v];
1538 }
1539
1540
1541 /* Identify a variant from the strings the chess servers use or the
1542    PGN Variant tag names we use. */
1543 VariantClass
1544 StringToVariant(e)
1545      char *e;
1546 {
1547     char *p;
1548     int wnum = -1;
1549     VariantClass v = VariantNormal;
1550     int i, found = FALSE;
1551     char buf[MSG_SIZ];
1552
1553     if (!e) return v;
1554
1555     /* [HGM] skip over optional board-size prefixes */
1556     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1557         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1558         while( *e++ != '_');
1559     }
1560
1561     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1562         v = VariantNormal;
1563         found = TRUE;
1564     } else
1565     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1566       if (StrCaseStr(e, variantNames[i])) {
1567         v = (VariantClass) i;
1568         found = TRUE;
1569         break;
1570       }
1571     }
1572
1573     if (!found) {
1574       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1575           || StrCaseStr(e, "wild/fr") 
1576           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1577         v = VariantFischeRandom;
1578       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1579                  (i = 1, p = StrCaseStr(e, "w"))) {
1580         p += i;
1581         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582         if (isdigit(*p)) {
1583           wnum = atoi(p);
1584         } else {
1585           wnum = -1;
1586         }
1587         switch (wnum) {
1588         case 0: /* FICS only, actually */
1589         case 1:
1590           /* Castling legal even if K starts on d-file */
1591           v = VariantWildCastle;
1592           break;
1593         case 2:
1594         case 3:
1595         case 4:
1596           /* Castling illegal even if K & R happen to start in
1597              normal positions. */
1598           v = VariantNoCastle;
1599           break;
1600         case 5:
1601         case 7:
1602         case 8:
1603         case 10:
1604         case 11:
1605         case 12:
1606         case 13:
1607         case 14:
1608         case 15:
1609         case 18:
1610         case 19:
1611           /* Castling legal iff K & R start in normal positions */
1612           v = VariantNormal;
1613           break;
1614         case 6:
1615         case 20:
1616         case 21:
1617           /* Special wilds for position setup; unclear what to do here */
1618           v = VariantLoadable;
1619           break;
1620         case 9:
1621           /* Bizarre ICC game */
1622           v = VariantTwoKings;
1623           break;
1624         case 16:
1625           v = VariantKriegspiel;
1626           break;
1627         case 17:
1628           v = VariantLosers;
1629           break;
1630         case 22:
1631           v = VariantFischeRandom;
1632           break;
1633         case 23:
1634           v = VariantCrazyhouse;
1635           break;
1636         case 24:
1637           v = VariantBughouse;
1638           break;
1639         case 25:
1640           v = Variant3Check;
1641           break;
1642         case 26:
1643           /* Not quite the same as FICS suicide! */
1644           v = VariantGiveaway;
1645           break;
1646         case 27:
1647           v = VariantAtomic;
1648           break;
1649         case 28:
1650           v = VariantShatranj;
1651           break;
1652
1653         /* Temporary names for future ICC types.  The name *will* change in 
1654            the next xboard/WinBoard release after ICC defines it. */
1655         case 29:
1656           v = Variant29;
1657           break;
1658         case 30:
1659           v = Variant30;
1660           break;
1661         case 31:
1662           v = Variant31;
1663           break;
1664         case 32:
1665           v = Variant32;
1666           break;
1667         case 33:
1668           v = Variant33;
1669           break;
1670         case 34:
1671           v = Variant34;
1672           break;
1673         case 35:
1674           v = Variant35;
1675           break;
1676         case 36:
1677           v = Variant36;
1678           break;
1679         case 37:
1680           v = VariantShogi;
1681           break;
1682         case 38:
1683           v = VariantXiangqi;
1684           break;
1685         case 39:
1686           v = VariantCourier;
1687           break;
1688         case 40:
1689           v = VariantGothic;
1690           break;
1691         case 41:
1692           v = VariantCapablanca;
1693           break;
1694         case 42:
1695           v = VariantKnightmate;
1696           break;
1697         case 43:
1698           v = VariantFairy;
1699           break;
1700         case 44:
1701           v = VariantCylinder;
1702           break;
1703         case 45:
1704           v = VariantFalcon;
1705           break;
1706         case 46:
1707           v = VariantCapaRandom;
1708           break;
1709         case 47:
1710           v = VariantBerolina;
1711           break;
1712         case 48:
1713           v = VariantJanus;
1714           break;
1715         case 49:
1716           v = VariantSuper;
1717           break;
1718         case 50:
1719           v = VariantGreat;
1720           break;
1721         case -1:
1722           /* Found "wild" or "w" in the string but no number;
1723              must assume it's normal chess. */
1724           v = VariantNormal;
1725           break;
1726         default:
1727           sprintf(buf, _("Unknown wild type %d"), wnum);
1728           DisplayError(buf, 0);
1729           v = VariantUnknown;
1730           break;
1731         }
1732       }
1733     }
1734     if (appData.debugMode) {
1735       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1736               e, wnum, VariantName(v));
1737     }
1738     return v;
1739 }
1740
1741 static int leftover_start = 0, leftover_len = 0;
1742 char star_match[STAR_MATCH_N][MSG_SIZ];
1743
1744 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1745    advance *index beyond it, and set leftover_start to the new value of
1746    *index; else return FALSE.  If pattern contains the character '*', it
1747    matches any sequence of characters not containing '\r', '\n', or the
1748    character following the '*' (if any), and the matched sequence(s) are
1749    copied into star_match.
1750    */
1751 int
1752 looking_at(buf, index, pattern)
1753      char *buf;
1754      int *index;
1755      char *pattern;
1756 {
1757     char *bufp = &buf[*index], *patternp = pattern;
1758     int star_count = 0;
1759     char *matchp = star_match[0];
1760     
1761     for (;;) {
1762         if (*patternp == NULLCHAR) {
1763             *index = leftover_start = bufp - buf;
1764             *matchp = NULLCHAR;
1765             return TRUE;
1766         }
1767         if (*bufp == NULLCHAR) return FALSE;
1768         if (*patternp == '*') {
1769             if (*bufp == *(patternp + 1)) {
1770                 *matchp = NULLCHAR;
1771                 matchp = star_match[++star_count];
1772                 patternp += 2;
1773                 bufp++;
1774                 continue;
1775             } else if (*bufp == '\n' || *bufp == '\r') {
1776                 patternp++;
1777                 if (*patternp == NULLCHAR)
1778                   continue;
1779                 else
1780                   return FALSE;
1781             } else {
1782                 *matchp++ = *bufp++;
1783                 continue;
1784             }
1785         }
1786         if (*patternp != *bufp) return FALSE;
1787         patternp++;
1788         bufp++;
1789     }
1790 }
1791
1792 void
1793 SendToPlayer(data, length)
1794      char *data;
1795      int length;
1796 {
1797     int error, outCount;
1798     outCount = OutputToProcess(NoProc, data, length, &error);
1799     if (outCount < length) {
1800         DisplayFatalError(_("Error writing to display"), error, 1);
1801     }
1802 }
1803
1804 void
1805 PackHolding(packed, holding)
1806      char packed[];
1807      char *holding;
1808 {
1809     char *p = holding;
1810     char *q = packed;
1811     int runlength = 0;
1812     int curr = 9999;
1813     do {
1814         if (*p == curr) {
1815             runlength++;
1816         } else {
1817             switch (runlength) {
1818               case 0:
1819                 break;
1820               case 1:
1821                 *q++ = curr;
1822                 break;
1823               case 2:
1824                 *q++ = curr;
1825                 *q++ = curr;
1826                 break;
1827               default:
1828                 sprintf(q, "%d", runlength);
1829                 while (*q) q++;
1830                 *q++ = curr;
1831                 break;
1832             }
1833             runlength = 1;
1834             curr = *p;
1835         }
1836     } while (*p++);
1837     *q = NULLCHAR;
1838 }
1839
1840 /* Telnet protocol requests from the front end */
1841 void
1842 TelnetRequest(ddww, option)
1843      unsigned char ddww, option;
1844 {
1845     unsigned char msg[3];
1846     int outCount, outError;
1847
1848     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1849
1850     if (appData.debugMode) {
1851         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1852         switch (ddww) {
1853           case TN_DO:
1854             ddwwStr = "DO";
1855             break;
1856           case TN_DONT:
1857             ddwwStr = "DONT";
1858             break;
1859           case TN_WILL:
1860             ddwwStr = "WILL";
1861             break;
1862           case TN_WONT:
1863             ddwwStr = "WONT";
1864             break;
1865           default:
1866             ddwwStr = buf1;
1867             sprintf(buf1, "%d", ddww);
1868             break;
1869         }
1870         switch (option) {
1871           case TN_ECHO:
1872             optionStr = "ECHO";
1873             break;
1874           default:
1875             optionStr = buf2;
1876             sprintf(buf2, "%d", option);
1877             break;
1878         }
1879         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1880     }
1881     msg[0] = TN_IAC;
1882     msg[1] = ddww;
1883     msg[2] = option;
1884     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1885     if (outCount < 3) {
1886         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887     }
1888 }
1889
1890 void
1891 DoEcho()
1892 {
1893     if (!appData.icsActive) return;
1894     TelnetRequest(TN_DO, TN_ECHO);
1895 }
1896
1897 void
1898 DontEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DONT, TN_ECHO);
1902 }
1903
1904 void
1905 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1906 {
1907     /* put the holdings sent to us by the server on the board holdings area */
1908     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1909     char p;
1910     ChessSquare piece;
1911
1912     if(gameInfo.holdingsWidth < 2)  return;
1913     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1914         return; // prevent overwriting by pre-board holdings
1915
1916     if( (int)lowestPiece >= BlackPawn ) {
1917         holdingsColumn = 0;
1918         countsColumn = 1;
1919         holdingsStartRow = BOARD_HEIGHT-1;
1920         direction = -1;
1921     } else {
1922         holdingsColumn = BOARD_WIDTH-1;
1923         countsColumn = BOARD_WIDTH-2;
1924         holdingsStartRow = 0;
1925         direction = 1;
1926     }
1927
1928     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1929         board[i][holdingsColumn] = EmptySquare;
1930         board[i][countsColumn]   = (ChessSquare) 0;
1931     }
1932     while( (p=*holdings++) != NULLCHAR ) {
1933         piece = CharToPiece( ToUpper(p) );
1934         if(piece == EmptySquare) continue;
1935         /*j = (int) piece - (int) WhitePawn;*/
1936         j = PieceToNumber(piece);
1937         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1938         if(j < 0) continue;               /* should not happen */
1939         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1940         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1941         board[holdingsStartRow+j*direction][countsColumn]++;
1942     }
1943 }
1944
1945
1946 void
1947 VariantSwitch(Board board, VariantClass newVariant)
1948 {
1949    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1950    Board oldBoard;
1951
1952    startedFromPositionFile = FALSE;
1953    if(gameInfo.variant == newVariant) return;
1954
1955    /* [HGM] This routine is called each time an assignment is made to
1956     * gameInfo.variant during a game, to make sure the board sizes
1957     * are set to match the new variant. If that means adding or deleting
1958     * holdings, we shift the playing board accordingly
1959     * This kludge is needed because in ICS observe mode, we get boards
1960     * of an ongoing game without knowing the variant, and learn about the
1961     * latter only later. This can be because of the move list we requested,
1962     * in which case the game history is refilled from the beginning anyway,
1963     * but also when receiving holdings of a crazyhouse game. In the latter
1964     * case we want to add those holdings to the already received position.
1965     */
1966
1967    
1968    if (appData.debugMode) {
1969      fprintf(debugFP, "Switch board from %s to %s\n",
1970              VariantName(gameInfo.variant), VariantName(newVariant));
1971      setbuf(debugFP, NULL);
1972    }
1973    shuffleOpenings = 0;       /* [HGM] shuffle */
1974    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1975    switch(newVariant) 
1976      {
1977      case VariantShogi:
1978        newWidth = 9;  newHeight = 9;
1979        gameInfo.holdingsSize = 7;
1980      case VariantBughouse:
1981      case VariantCrazyhouse:
1982        newHoldingsWidth = 2; break;
1983      case VariantGreat:
1984        newWidth = 10;
1985      case VariantSuper:
1986        newHoldingsWidth = 2;
1987        gameInfo.holdingsSize = 8;
1988        break;
1989      case VariantGothic:
1990      case VariantCapablanca:
1991      case VariantCapaRandom:
1992        newWidth = 10;
1993      default:
1994        newHoldingsWidth = gameInfo.holdingsSize = 0;
1995      };
1996    
1997    if(newWidth  != gameInfo.boardWidth  ||
1998       newHeight != gameInfo.boardHeight ||
1999       newHoldingsWidth != gameInfo.holdingsWidth ) {
2000      
2001      /* shift position to new playing area, if needed */
2002      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++) 
2004          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007        for(i=0; i<newHeight; i++) {
2008          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2009          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2010        }
2011      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2012        for(i=0; i<BOARD_HEIGHT; i++)
2013          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2014            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015              board[i][j];
2016      }
2017      gameInfo.boardWidth  = newWidth;
2018      gameInfo.boardHeight = newHeight;
2019      gameInfo.holdingsWidth = newHoldingsWidth;
2020      gameInfo.variant = newVariant;
2021      InitDrawingSizes(-2, 0);
2022    } else gameInfo.variant = newVariant;
2023    CopyBoard(oldBoard, board);   // remember correctly formatted board
2024      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2025    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2026 }
2027
2028 static int loggedOn = FALSE;
2029
2030 /*-- Game start info cache: --*/
2031 int gs_gamenum;
2032 char gs_kind[MSG_SIZ];
2033 static char player1Name[128] = "";
2034 static char player2Name[128] = "";
2035 static char cont_seq[] = "\n\\   ";
2036 static int player1Rating = -1;
2037 static int player2Rating = -1;
2038 /*----------------------------*/
2039
2040 ColorClass curColor = ColorNormal;
2041 int suppressKibitz = 0;
2042
2043 void
2044 read_from_ics(isr, closure, data, count, error)
2045      InputSourceRef isr;
2046      VOIDSTAR closure;
2047      char *data;
2048      int count;
2049      int error;
2050 {
2051 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2052 #define STARTED_NONE 0
2053 #define STARTED_MOVES 1
2054 #define STARTED_BOARD 2
2055 #define STARTED_OBSERVE 3
2056 #define STARTED_HOLDINGS 4
2057 #define STARTED_CHATTER 5
2058 #define STARTED_COMMENT 6
2059 #define STARTED_MOVES_NOHIDE 7
2060     
2061     static int started = STARTED_NONE;
2062     static char parse[20000];
2063     static int parse_pos = 0;
2064     static char buf[BUF_SIZE + 1];
2065     static int firstTime = TRUE, intfSet = FALSE;
2066     static ColorClass prevColor = ColorNormal;
2067     static int savingComment = FALSE;
2068     static int cmatch = 0; // continuation sequence match
2069     char *bp;
2070     char str[500];
2071     int i, oldi;
2072     int buf_len;
2073     int next_out;
2074     int tkind;
2075     int backup;    /* [DM] For zippy color lines */
2076     char *p;
2077     char talker[MSG_SIZ]; // [HGM] chat
2078     int channel;
2079
2080     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2081
2082     if (appData.debugMode) {
2083       if (!error) {
2084         fprintf(debugFP, "<ICS: ");
2085         show_bytes(debugFP, data, count);
2086         fprintf(debugFP, "\n");
2087       }
2088     }
2089
2090     if (appData.debugMode) { int f = forwardMostMove;
2091         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2092                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2093     }
2094     if (count > 0) {
2095         /* If last read ended with a partial line that we couldn't parse,
2096            prepend it to the new read and try again. */
2097         if (leftover_len > 0) {
2098             for (i=0; i<leftover_len; i++)
2099               buf[i] = buf[leftover_start + i];
2100         }
2101
2102     /* copy new characters into the buffer */
2103     bp = buf + leftover_len;
2104     buf_len=leftover_len;
2105     for (i=0; i<count; i++)
2106     {
2107         // ignore these
2108         if (data[i] == '\r')
2109             continue;
2110
2111         // join lines split by ICS?
2112         if (!appData.noJoin)
2113         {
2114             /*
2115                 Joining just consists of finding matches against the
2116                 continuation sequence, and discarding that sequence
2117                 if found instead of copying it.  So, until a match
2118                 fails, there's nothing to do since it might be the
2119                 complete sequence, and thus, something we don't want
2120                 copied.
2121             */
2122             if (data[i] == cont_seq[cmatch])
2123             {
2124                 cmatch++;
2125                 if (cmatch == strlen(cont_seq))
2126                 {
2127                     cmatch = 0; // complete match.  just reset the counter
2128
2129                     /*
2130                         it's possible for the ICS to not include the space
2131                         at the end of the last word, making our [correct]
2132                         join operation fuse two separate words.  the server
2133                         does this when the space occurs at the width setting.
2134                     */
2135                     if (!buf_len || buf[buf_len-1] != ' ')
2136                     {
2137                         *bp++ = ' ';
2138                         buf_len++;
2139                     }
2140                 }
2141                 continue;
2142             }
2143             else if (cmatch)
2144             {
2145                 /*
2146                     match failed, so we have to copy what matched before
2147                     falling through and copying this character.  In reality,
2148                     this will only ever be just the newline character, but
2149                     it doesn't hurt to be precise.
2150                 */
2151                 strncpy(bp, cont_seq, cmatch);
2152                 bp += cmatch;
2153                 buf_len += cmatch;
2154                 cmatch = 0;
2155             }
2156         }
2157
2158         // copy this char
2159         *bp++ = data[i];
2160         buf_len++;
2161     }
2162
2163         buf[buf_len] = NULLCHAR;
2164         next_out = leftover_len;
2165         leftover_start = 0;
2166         
2167         i = 0;
2168         while (i < buf_len) {
2169             /* Deal with part of the TELNET option negotiation
2170                protocol.  We refuse to do anything beyond the
2171                defaults, except that we allow the WILL ECHO option,
2172                which ICS uses to turn off password echoing when we are
2173                directly connected to it.  We reject this option
2174                if localLineEditing mode is on (always on in xboard)
2175                and we are talking to port 23, which might be a real
2176                telnet server that will try to keep WILL ECHO on permanently.
2177              */
2178             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2179                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2180                 unsigned char option;
2181                 oldi = i;
2182                 switch ((unsigned char) buf[++i]) {
2183                   case TN_WILL:
2184                     if (appData.debugMode)
2185                       fprintf(debugFP, "\n<WILL ");
2186                     switch (option = (unsigned char) buf[++i]) {
2187                       case TN_ECHO:
2188                         if (appData.debugMode)
2189                           fprintf(debugFP, "ECHO ");
2190                         /* Reply only if this is a change, according
2191                            to the protocol rules. */
2192                         if (remoteEchoOption) break;
2193                         if (appData.localLineEditing &&
2194                             atoi(appData.icsPort) == TN_PORT) {
2195                             TelnetRequest(TN_DONT, TN_ECHO);
2196                         } else {
2197                             EchoOff();
2198                             TelnetRequest(TN_DO, TN_ECHO);
2199                             remoteEchoOption = TRUE;
2200                         }
2201                         break;
2202                       default:
2203                         if (appData.debugMode)
2204                           fprintf(debugFP, "%d ", option);
2205                         /* Whatever this is, we don't want it. */
2206                         TelnetRequest(TN_DONT, option);
2207                         break;
2208                     }
2209                     break;
2210                   case TN_WONT:
2211                     if (appData.debugMode)
2212                       fprintf(debugFP, "\n<WONT ");
2213                     switch (option = (unsigned char) buf[++i]) {
2214                       case TN_ECHO:
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "ECHO ");
2217                         /* Reply only if this is a change, according
2218                            to the protocol rules. */
2219                         if (!remoteEchoOption) break;
2220                         EchoOn();
2221                         TelnetRequest(TN_DONT, TN_ECHO);
2222                         remoteEchoOption = FALSE;
2223                         break;
2224                       default:
2225                         if (appData.debugMode)
2226                           fprintf(debugFP, "%d ", (unsigned char) option);
2227                         /* Whatever this is, it must already be turned
2228                            off, because we never agree to turn on
2229                            anything non-default, so according to the
2230                            protocol rules, we don't reply. */
2231                         break;
2232                     }
2233                     break;
2234                   case TN_DO:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<DO ");
2237                     switch (option = (unsigned char) buf[++i]) {
2238                       default:
2239                         /* Whatever this is, we refuse to do it. */
2240                         if (appData.debugMode)
2241                           fprintf(debugFP, "%d ", option);
2242                         TelnetRequest(TN_WONT, option);
2243                         break;
2244                     }
2245                     break;
2246                   case TN_DONT:
2247                     if (appData.debugMode)
2248                       fprintf(debugFP, "\n<DONT ");
2249                     switch (option = (unsigned char) buf[++i]) {
2250                       default:
2251                         if (appData.debugMode)
2252                           fprintf(debugFP, "%d ", option);
2253                         /* Whatever this is, we are already not doing
2254                            it, because we never agree to do anything
2255                            non-default, so according to the protocol
2256                            rules, we don't reply. */
2257                         break;
2258                     }
2259                     break;
2260                   case TN_IAC:
2261                     if (appData.debugMode)
2262                       fprintf(debugFP, "\n<IAC ");
2263                     /* Doubled IAC; pass it through */
2264                     i--;
2265                     break;
2266                   default:
2267                     if (appData.debugMode)
2268                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2269                     /* Drop all other telnet commands on the floor */
2270                     break;
2271                 }
2272                 if (oldi > next_out)
2273                   SendToPlayer(&buf[next_out], oldi - next_out);
2274                 if (++i > next_out)
2275                   next_out = i;
2276                 continue;
2277             }
2278                 
2279             /* OK, this at least will *usually* work */
2280             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2281                 loggedOn = TRUE;
2282             }
2283             
2284             if (loggedOn && !intfSet) {
2285                 if (ics_type == ICS_ICC) {
2286                   sprintf(str,
2287                           "/set-quietly interface %s\n/set-quietly style 12\n",
2288                           programVersion);
2289                 } else if (ics_type == ICS_CHESSNET) {
2290                   sprintf(str, "/style 12\n");
2291                 } else {
2292                   strcpy(str, "alias $ @\n$set interface ");
2293                   strcat(str, programVersion);
2294                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2295 #ifdef WIN32
2296                   strcat(str, "$iset nohighlight 1\n");
2297 #endif
2298                   strcat(str, "$iset lock 1\n$style 12\n");
2299                 }
2300                 SendToICS(str);
2301                 NotifyFrontendLogin();
2302                 intfSet = TRUE;
2303             }
2304
2305             if (started == STARTED_COMMENT) {
2306                 /* Accumulate characters in comment */
2307                 parse[parse_pos++] = buf[i];
2308                 if (buf[i] == '\n') {
2309                     parse[parse_pos] = NULLCHAR;
2310                     if(chattingPartner>=0) {
2311                         char mess[MSG_SIZ];
2312                         sprintf(mess, "%s%s", talker, parse);
2313                         OutputChatMessage(chattingPartner, mess);
2314                         chattingPartner = -1;
2315                     } else
2316                     if(!suppressKibitz) // [HGM] kibitz
2317                         AppendComment(forwardMostMove, StripHighlight(parse));
2318                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2319                         int nrDigit = 0, nrAlph = 0, i;
2320                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2321                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2322                         parse[parse_pos] = NULLCHAR;
2323                         // try to be smart: if it does not look like search info, it should go to
2324                         // ICS interaction window after all, not to engine-output window.
2325                         for(i=0; i<parse_pos; i++) { // count letters and digits
2326                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2327                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2328                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2329                         }
2330                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2331                             int depth=0; float score;
2332                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2333                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2334                                 pvInfoList[forwardMostMove-1].depth = depth;
2335                                 pvInfoList[forwardMostMove-1].score = 100*score;
2336                             }
2337                             OutputKibitz(suppressKibitz, parse);
2338                         } else {
2339                             char tmp[MSG_SIZ];
2340                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2341                             SendToPlayer(tmp, strlen(tmp));
2342                         }
2343                     }
2344                     started = STARTED_NONE;
2345                 } else {
2346                     /* Don't match patterns against characters in chatter */
2347                     i++;
2348                     continue;
2349                 }
2350             }
2351             if (started == STARTED_CHATTER) {
2352                 if (buf[i] != '\n') {
2353                     /* Don't match patterns against characters in chatter */
2354                     i++;
2355                     continue;
2356                 }
2357                 started = STARTED_NONE;
2358             }
2359
2360             /* Kludge to deal with rcmd protocol */
2361             if (firstTime && looking_at(buf, &i, "\001*")) {
2362                 DisplayFatalError(&buf[1], 0, 1);
2363                 continue;
2364             } else {
2365                 firstTime = FALSE;
2366             }
2367
2368             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2369                 ics_type = ICS_ICC;
2370                 ics_prefix = "/";
2371                 if (appData.debugMode)
2372                   fprintf(debugFP, "ics_type %d\n", ics_type);
2373                 continue;
2374             }
2375             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2376                 ics_type = ICS_FICS;
2377                 ics_prefix = "$";
2378                 if (appData.debugMode)
2379                   fprintf(debugFP, "ics_type %d\n", ics_type);
2380                 continue;
2381             }
2382             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2383                 ics_type = ICS_CHESSNET;
2384                 ics_prefix = "/";
2385                 if (appData.debugMode)
2386                   fprintf(debugFP, "ics_type %d\n", ics_type);
2387                 continue;
2388             }
2389
2390             if (!loggedOn &&
2391                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2392                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2393                  looking_at(buf, &i, "will be \"*\""))) {
2394               strcpy(ics_handle, star_match[0]);
2395               continue;
2396             }
2397
2398             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2399               char buf[MSG_SIZ];
2400               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2401               DisplayIcsInteractionTitle(buf);
2402               have_set_title = TRUE;
2403             }
2404
2405             /* skip finger notes */
2406             if (started == STARTED_NONE &&
2407                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2408                  (buf[i] == '1' && buf[i+1] == '0')) &&
2409                 buf[i+2] == ':' && buf[i+3] == ' ') {
2410               started = STARTED_CHATTER;
2411               i += 3;
2412               continue;
2413             }
2414
2415             /* skip formula vars */
2416             if (started == STARTED_NONE &&
2417                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2418               started = STARTED_CHATTER;
2419               i += 3;
2420               continue;
2421             }
2422
2423             oldi = i;
2424             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2425             if (appData.autoKibitz && started == STARTED_NONE && 
2426                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2427                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2428                 if(looking_at(buf, &i, "* kibitzes: ") &&
2429                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2430                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2431                         suppressKibitz = TRUE;
2432                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2433                                 && (gameMode == IcsPlayingWhite)) ||
2434                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2435                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2436                             started = STARTED_CHATTER; // own kibitz we simply discard
2437                         else {
2438                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2439                             parse_pos = 0; parse[0] = NULLCHAR;
2440                             savingComment = TRUE;
2441                             suppressKibitz = gameMode != IcsObserving ? 2 :
2442                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2443                         } 
2444                         continue;
2445                 } else
2446                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2447                     started = STARTED_CHATTER;
2448                     suppressKibitz = TRUE;
2449                 }
2450             } // [HGM] kibitz: end of patch
2451
2452 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2453
2454             // [HGM] chat: intercept tells by users for which we have an open chat window
2455             channel = -1;
2456             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2457                                            looking_at(buf, &i, "* whispers:") ||
2458                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2459                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2460                 int p;
2461                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2462                 chattingPartner = -1;
2463
2464                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2465                 for(p=0; p<MAX_CHAT; p++) {
2466                     if(channel == atoi(chatPartner[p])) {
2467                     talker[0] = '['; strcat(talker, "] ");
2468                     chattingPartner = p; break;
2469                     }
2470                 } else
2471                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2472                 for(p=0; p<MAX_CHAT; p++) {
2473                     if(!strcmp("WHISPER", chatPartner[p])) {
2474                         talker[0] = '['; strcat(talker, "] ");
2475                         chattingPartner = p; break;
2476                     }
2477                 }
2478                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2479                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2480                     talker[0] = 0;
2481                     chattingPartner = p; break;
2482                 }
2483                 if(chattingPartner<0) i = oldi; else {
2484                     started = STARTED_COMMENT;
2485                     parse_pos = 0; parse[0] = NULLCHAR;
2486                     savingComment = 3 + chattingPartner; // counts as TRUE
2487                     suppressKibitz = TRUE;
2488                 }
2489             } // [HGM] chat: end of patch
2490
2491             if (appData.zippyTalk || appData.zippyPlay) {
2492                 /* [DM] Backup address for color zippy lines */
2493                 backup = i;
2494 #if ZIPPY
2495        #ifdef WIN32
2496                if (loggedOn == TRUE)
2497                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2498                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2499        #else
2500                 if (ZippyControl(buf, &i) ||
2501                     ZippyConverse(buf, &i) ||
2502                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2503                       loggedOn = TRUE;
2504                       if (!appData.colorize) continue;
2505                 }
2506        #endif
2507 #endif
2508             } // [DM] 'else { ' deleted
2509                 if (
2510                     /* Regular tells and says */
2511                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2512                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2513                     looking_at(buf, &i, "* says: ") ||
2514                     /* Don't color "message" or "messages" output */
2515                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2516                     looking_at(buf, &i, "*. * at *:*: ") ||
2517                     looking_at(buf, &i, "--* (*:*): ") ||
2518                     /* Message notifications (same color as tells) */
2519                     looking_at(buf, &i, "* has left a message ") ||
2520                     looking_at(buf, &i, "* just sent you a message:\n") ||
2521                     /* Whispers and kibitzes */
2522                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2523                     looking_at(buf, &i, "* kibitzes: ") ||
2524                     /* Channel tells */
2525                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2526
2527                   if (tkind == 1 && strchr(star_match[0], ':')) {
2528                       /* Avoid "tells you:" spoofs in channels */
2529                      tkind = 3;
2530                   }
2531                   if (star_match[0][0] == NULLCHAR ||
2532                       strchr(star_match[0], ' ') ||
2533                       (tkind == 3 && strchr(star_match[1], ' '))) {
2534                     /* Reject bogus matches */
2535                     i = oldi;
2536                   } else {
2537                     if (appData.colorize) {
2538                       if (oldi > next_out) {
2539                         SendToPlayer(&buf[next_out], oldi - next_out);
2540                         next_out = oldi;
2541                       }
2542                       switch (tkind) {
2543                       case 1:
2544                         Colorize(ColorTell, FALSE);
2545                         curColor = ColorTell;
2546                         break;
2547                       case 2:
2548                         Colorize(ColorKibitz, FALSE);
2549                         curColor = ColorKibitz;
2550                         break;
2551                       case 3:
2552                         p = strrchr(star_match[1], '(');
2553                         if (p == NULL) {
2554                           p = star_match[1];
2555                         } else {
2556                           p++;
2557                         }
2558                         if (atoi(p) == 1) {
2559                           Colorize(ColorChannel1, FALSE);
2560                           curColor = ColorChannel1;
2561                         } else {
2562                           Colorize(ColorChannel, FALSE);
2563                           curColor = ColorChannel;
2564                         }
2565                         break;
2566                       case 5:
2567                         curColor = ColorNormal;
2568                         break;
2569                       }
2570                     }
2571                     if (started == STARTED_NONE && appData.autoComment &&
2572                         (gameMode == IcsObserving ||
2573                          gameMode == IcsPlayingWhite ||
2574                          gameMode == IcsPlayingBlack)) {
2575                       parse_pos = i - oldi;
2576                       memcpy(parse, &buf[oldi], parse_pos);
2577                       parse[parse_pos] = NULLCHAR;
2578                       started = STARTED_COMMENT;
2579                       savingComment = TRUE;
2580                     } else {
2581                       started = STARTED_CHATTER;
2582                       savingComment = FALSE;
2583                     }
2584                     loggedOn = TRUE;
2585                     continue;
2586                   }
2587                 }
2588
2589                 if (looking_at(buf, &i, "* s-shouts: ") ||
2590                     looking_at(buf, &i, "* c-shouts: ")) {
2591                     if (appData.colorize) {
2592                         if (oldi > next_out) {
2593                             SendToPlayer(&buf[next_out], oldi - next_out);
2594                             next_out = oldi;
2595                         }
2596                         Colorize(ColorSShout, FALSE);
2597                         curColor = ColorSShout;
2598                     }
2599                     loggedOn = TRUE;
2600                     started = STARTED_CHATTER;
2601                     continue;
2602                 }
2603
2604                 if (looking_at(buf, &i, "--->")) {
2605                     loggedOn = TRUE;
2606                     continue;
2607                 }
2608
2609                 if (looking_at(buf, &i, "* shouts: ") ||
2610                     looking_at(buf, &i, "--> ")) {
2611                     if (appData.colorize) {
2612                         if (oldi > next_out) {
2613                             SendToPlayer(&buf[next_out], oldi - next_out);
2614                             next_out = oldi;
2615                         }
2616                         Colorize(ColorShout, FALSE);
2617                         curColor = ColorShout;
2618                     }
2619                     loggedOn = TRUE;
2620                     started = STARTED_CHATTER;
2621                     continue;
2622                 }
2623
2624                 if (looking_at( buf, &i, "Challenge:")) {
2625                     if (appData.colorize) {
2626                         if (oldi > next_out) {
2627                             SendToPlayer(&buf[next_out], oldi - next_out);
2628                             next_out = oldi;
2629                         }
2630                         Colorize(ColorChallenge, FALSE);
2631                         curColor = ColorChallenge;
2632                     }
2633                     loggedOn = TRUE;
2634                     continue;
2635                 }
2636
2637                 if (looking_at(buf, &i, "* offers you") ||
2638                     looking_at(buf, &i, "* offers to be") ||
2639                     looking_at(buf, &i, "* would like to") ||
2640                     looking_at(buf, &i, "* requests to") ||
2641                     looking_at(buf, &i, "Your opponent offers") ||
2642                     looking_at(buf, &i, "Your opponent requests")) {
2643
2644                     if (appData.colorize) {
2645                         if (oldi > next_out) {
2646                             SendToPlayer(&buf[next_out], oldi - next_out);
2647                             next_out = oldi;
2648                         }
2649                         Colorize(ColorRequest, FALSE);
2650                         curColor = ColorRequest;
2651                     }
2652                     continue;
2653                 }
2654
2655                 if (looking_at(buf, &i, "* (*) seeking")) {
2656                     if (appData.colorize) {
2657                         if (oldi > next_out) {
2658                             SendToPlayer(&buf[next_out], oldi - next_out);
2659                             next_out = oldi;
2660                         }
2661                         Colorize(ColorSeek, FALSE);
2662                         curColor = ColorSeek;
2663                     }
2664                     continue;
2665             }
2666
2667             if (looking_at(buf, &i, "\\   ")) {
2668                 if (prevColor != ColorNormal) {
2669                     if (oldi > next_out) {
2670                         SendToPlayer(&buf[next_out], oldi - next_out);
2671                         next_out = oldi;
2672                     }
2673                     Colorize(prevColor, TRUE);
2674                     curColor = prevColor;
2675                 }
2676                 if (savingComment) {
2677                     parse_pos = i - oldi;
2678                     memcpy(parse, &buf[oldi], parse_pos);
2679                     parse[parse_pos] = NULLCHAR;
2680                     started = STARTED_COMMENT;
2681                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2682                         chattingPartner = savingComment - 3; // kludge to remember the box
2683                 } else {
2684                     started = STARTED_CHATTER;
2685                 }
2686                 continue;
2687             }
2688
2689             if (looking_at(buf, &i, "Black Strength :") ||
2690                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2691                 looking_at(buf, &i, "<10>") ||
2692                 looking_at(buf, &i, "#@#")) {
2693                 /* Wrong board style */
2694                 loggedOn = TRUE;
2695                 SendToICS(ics_prefix);
2696                 SendToICS("set style 12\n");
2697                 SendToICS(ics_prefix);
2698                 SendToICS("refresh\n");
2699                 continue;
2700             }
2701             
2702             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2703                 ICSInitScript();
2704                 have_sent_ICS_logon = 1;
2705                 continue;
2706             }
2707               
2708             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2709                 (looking_at(buf, &i, "\n<12> ") ||
2710                  looking_at(buf, &i, "<12> "))) {
2711                 loggedOn = TRUE;
2712                 if (oldi > next_out) {
2713                     SendToPlayer(&buf[next_out], oldi - next_out);
2714                 }
2715                 next_out = i;
2716                 started = STARTED_BOARD;
2717                 parse_pos = 0;
2718                 continue;
2719             }
2720
2721             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2722                 looking_at(buf, &i, "<b1> ")) {
2723                 if (oldi > next_out) {
2724                     SendToPlayer(&buf[next_out], oldi - next_out);
2725                 }
2726                 next_out = i;
2727                 started = STARTED_HOLDINGS;
2728                 parse_pos = 0;
2729                 continue;
2730             }
2731
2732             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2733                 loggedOn = TRUE;
2734                 /* Header for a move list -- first line */
2735
2736                 switch (ics_getting_history) {
2737                   case H_FALSE:
2738                     switch (gameMode) {
2739                       case IcsIdle:
2740                       case BeginningOfGame:
2741                         /* User typed "moves" or "oldmoves" while we
2742                            were idle.  Pretend we asked for these
2743                            moves and soak them up so user can step
2744                            through them and/or save them.
2745                            */
2746                         Reset(FALSE, TRUE);
2747                         gameMode = IcsObserving;
2748                         ModeHighlight();
2749                         ics_gamenum = -1;
2750                         ics_getting_history = H_GOT_UNREQ_HEADER;
2751                         break;
2752                       case EditGame: /*?*/
2753                       case EditPosition: /*?*/
2754                         /* Should above feature work in these modes too? */
2755                         /* For now it doesn't */
2756                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2757                         break;
2758                       default:
2759                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2760                         break;
2761                     }
2762                     break;
2763                   case H_REQUESTED:
2764                     /* Is this the right one? */
2765                     if (gameInfo.white && gameInfo.black &&
2766                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2767                         strcmp(gameInfo.black, star_match[2]) == 0) {
2768                         /* All is well */
2769                         ics_getting_history = H_GOT_REQ_HEADER;
2770                     }
2771                     break;
2772                   case H_GOT_REQ_HEADER:
2773                   case H_GOT_UNREQ_HEADER:
2774                   case H_GOT_UNWANTED_HEADER:
2775                   case H_GETTING_MOVES:
2776                     /* Should not happen */
2777                     DisplayError(_("Error gathering move list: two headers"), 0);
2778                     ics_getting_history = H_FALSE;
2779                     break;
2780                 }
2781
2782                 /* Save player ratings into gameInfo if needed */
2783                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2784                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2785                     (gameInfo.whiteRating == -1 ||
2786                      gameInfo.blackRating == -1)) {
2787
2788                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2789                     gameInfo.blackRating = string_to_rating(star_match[3]);
2790                     if (appData.debugMode)
2791                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2792                               gameInfo.whiteRating, gameInfo.blackRating);
2793                 }
2794                 continue;
2795             }
2796
2797             if (looking_at(buf, &i,
2798               "* * match, initial time: * minute*, increment: * second")) {
2799                 /* Header for a move list -- second line */
2800                 /* Initial board will follow if this is a wild game */
2801                 if (gameInfo.event != NULL) free(gameInfo.event);
2802                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2803                 gameInfo.event = StrSave(str);
2804                 /* [HGM] we switched variant. Translate boards if needed. */
2805                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2806                 continue;
2807             }
2808
2809             if (looking_at(buf, &i, "Move  ")) {
2810                 /* Beginning of a move list */
2811                 switch (ics_getting_history) {
2812                   case H_FALSE:
2813                     /* Normally should not happen */
2814                     /* Maybe user hit reset while we were parsing */
2815                     break;
2816                   case H_REQUESTED:
2817                     /* Happens if we are ignoring a move list that is not
2818                      * the one we just requested.  Common if the user
2819                      * tries to observe two games without turning off
2820                      * getMoveList */
2821                     break;
2822                   case H_GETTING_MOVES:
2823                     /* Should not happen */
2824                     DisplayError(_("Error gathering move list: nested"), 0);
2825                     ics_getting_history = H_FALSE;
2826                     break;
2827                   case H_GOT_REQ_HEADER:
2828                     ics_getting_history = H_GETTING_MOVES;
2829                     started = STARTED_MOVES;
2830                     parse_pos = 0;
2831                     if (oldi > next_out) {
2832                         SendToPlayer(&buf[next_out], oldi - next_out);
2833                     }
2834                     break;
2835                   case H_GOT_UNREQ_HEADER:
2836                     ics_getting_history = H_GETTING_MOVES;
2837                     started = STARTED_MOVES_NOHIDE;
2838                     parse_pos = 0;
2839                     break;
2840                   case H_GOT_UNWANTED_HEADER:
2841                     ics_getting_history = H_FALSE;
2842                     break;
2843                 }
2844                 continue;
2845             }                           
2846             
2847             if (looking_at(buf, &i, "% ") ||
2848                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2849                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2850                 savingComment = FALSE;
2851                 switch (started) {
2852                   case STARTED_MOVES:
2853                   case STARTED_MOVES_NOHIDE:
2854                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2855                     parse[parse_pos + i - oldi] = NULLCHAR;
2856                     ParseGameHistory(parse);
2857 #if ZIPPY
2858                     if (appData.zippyPlay && first.initDone) {
2859                         FeedMovesToProgram(&first, forwardMostMove);
2860                         if (gameMode == IcsPlayingWhite) {
2861                             if (WhiteOnMove(forwardMostMove)) {
2862                                 if (first.sendTime) {
2863                                   if (first.useColors) {
2864                                     SendToProgram("black\n", &first); 
2865                                   }
2866                                   SendTimeRemaining(&first, TRUE);
2867                                 }
2868                                 if (first.useColors) {
2869                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2870                                 }
2871                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2872                                 first.maybeThinking = TRUE;
2873                             } else {
2874                                 if (first.usePlayother) {
2875                                   if (first.sendTime) {
2876                                     SendTimeRemaining(&first, TRUE);
2877                                   }
2878                                   SendToProgram("playother\n", &first);
2879                                   firstMove = FALSE;
2880                                 } else {
2881                                   firstMove = TRUE;
2882                                 }
2883                             }
2884                         } else if (gameMode == IcsPlayingBlack) {
2885                             if (!WhiteOnMove(forwardMostMove)) {
2886                                 if (first.sendTime) {
2887                                   if (first.useColors) {
2888                                     SendToProgram("white\n", &first);
2889                                   }
2890                                   SendTimeRemaining(&first, FALSE);
2891                                 }
2892                                 if (first.useColors) {
2893                                   SendToProgram("black\n", &first);
2894                                 }
2895                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2896                                 first.maybeThinking = TRUE;
2897                             } else {
2898                                 if (first.usePlayother) {
2899                                   if (first.sendTime) {
2900                                     SendTimeRemaining(&first, FALSE);
2901                                   }
2902                                   SendToProgram("playother\n", &first);
2903                                   firstMove = FALSE;
2904                                 } else {
2905                                   firstMove = TRUE;
2906                                 }
2907                             }
2908                         }                       
2909                     }
2910 #endif
2911                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2912                         /* Moves came from oldmoves or moves command
2913                            while we weren't doing anything else.
2914                            */
2915                         currentMove = forwardMostMove;
2916                         ClearHighlights();/*!!could figure this out*/
2917                         flipView = appData.flipView;
2918                         DrawPosition(TRUE, boards[currentMove]);
2919                         DisplayBothClocks();
2920                         sprintf(str, "%s vs. %s",
2921                                 gameInfo.white, gameInfo.black);
2922                         DisplayTitle(str);
2923                         gameMode = IcsIdle;
2924                     } else {
2925                         /* Moves were history of an active game */
2926                         if (gameInfo.resultDetails != NULL) {
2927                             free(gameInfo.resultDetails);
2928                             gameInfo.resultDetails = NULL;
2929                         }
2930                     }
2931                     HistorySet(parseList, backwardMostMove,
2932                                forwardMostMove, currentMove-1);
2933                     DisplayMove(currentMove - 1);
2934                     if (started == STARTED_MOVES) next_out = i;
2935                     started = STARTED_NONE;
2936                     ics_getting_history = H_FALSE;
2937                     break;
2938
2939                   case STARTED_OBSERVE:
2940                     started = STARTED_NONE;
2941                     SendToICS(ics_prefix);
2942                     SendToICS("refresh\n");
2943                     break;
2944
2945                   default:
2946                     break;
2947                 }
2948                 if(bookHit) { // [HGM] book: simulate book reply
2949                     static char bookMove[MSG_SIZ]; // a bit generous?
2950
2951                     programStats.nodes = programStats.depth = programStats.time = 
2952                     programStats.score = programStats.got_only_move = 0;
2953                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2954
2955                     strcpy(bookMove, "move ");
2956                     strcat(bookMove, bookHit);
2957                     HandleMachineMove(bookMove, &first);
2958                 }
2959                 continue;
2960             }
2961             
2962             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2963                  started == STARTED_HOLDINGS ||
2964                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2965                 /* Accumulate characters in move list or board */
2966                 parse[parse_pos++] = buf[i];
2967             }
2968             
2969             /* Start of game messages.  Mostly we detect start of game
2970                when the first board image arrives.  On some versions
2971                of the ICS, though, we need to do a "refresh" after starting
2972                to observe in order to get the current board right away. */
2973             if (looking_at(buf, &i, "Adding game * to observation list")) {
2974                 started = STARTED_OBSERVE;
2975                 continue;
2976             }
2977
2978             /* Handle auto-observe */
2979             if (appData.autoObserve &&
2980                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2981                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2982                 char *player;
2983                 /* Choose the player that was highlighted, if any. */
2984                 if (star_match[0][0] == '\033' ||
2985                     star_match[1][0] != '\033') {
2986                     player = star_match[0];
2987                 } else {
2988                     player = star_match[2];
2989                 }
2990                 sprintf(str, "%sobserve %s\n",
2991                         ics_prefix, StripHighlightAndTitle(player));
2992                 SendToICS(str);
2993
2994                 /* Save ratings from notify string */
2995                 strcpy(player1Name, star_match[0]);
2996                 player1Rating = string_to_rating(star_match[1]);
2997                 strcpy(player2Name, star_match[2]);
2998                 player2Rating = string_to_rating(star_match[3]);
2999
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, 
3002                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3003                           player1Name, player1Rating,
3004                           player2Name, player2Rating);
3005
3006                 continue;
3007             }
3008
3009             /* Deal with automatic examine mode after a game,
3010                and with IcsObserving -> IcsExamining transition */
3011             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3012                 looking_at(buf, &i, "has made you an examiner of game *")) {
3013
3014                 int gamenum = atoi(star_match[0]);
3015                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3016                     gamenum == ics_gamenum) {
3017                     /* We were already playing or observing this game;
3018                        no need to refetch history */
3019                     gameMode = IcsExamining;
3020                     if (pausing) {
3021                         pauseExamForwardMostMove = forwardMostMove;
3022                     } else if (currentMove < forwardMostMove) {
3023                         ForwardInner(forwardMostMove);
3024                     }
3025                 } else {
3026                     /* I don't think this case really can happen */
3027                     SendToICS(ics_prefix);
3028                     SendToICS("refresh\n");
3029                 }
3030                 continue;
3031             }    
3032             
3033             /* Error messages */
3034 //          if (ics_user_moved) {
3035             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3036                 if (looking_at(buf, &i, "Illegal move") ||
3037                     looking_at(buf, &i, "Not a legal move") ||
3038                     looking_at(buf, &i, "Your king is in check") ||
3039                     looking_at(buf, &i, "It isn't your turn") ||
3040                     looking_at(buf, &i, "It is not your move")) {
3041                     /* Illegal move */
3042                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3043                         currentMove = --forwardMostMove;
3044                         DisplayMove(currentMove - 1); /* before DMError */
3045                         DrawPosition(FALSE, boards[currentMove]);
3046                         SwitchClocks();
3047                         DisplayBothClocks();
3048                     }
3049                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3050                     ics_user_moved = 0;
3051                     continue;
3052                 }
3053             }
3054
3055             if (looking_at(buf, &i, "still have time") ||
3056                 looking_at(buf, &i, "not out of time") ||
3057                 looking_at(buf, &i, "either player is out of time") ||
3058                 looking_at(buf, &i, "has timeseal; checking")) {
3059                 /* We must have called his flag a little too soon */
3060                 whiteFlag = blackFlag = FALSE;
3061                 continue;
3062             }
3063
3064             if (looking_at(buf, &i, "added * seconds to") ||
3065                 looking_at(buf, &i, "seconds were added to")) {
3066                 /* Update the clocks */
3067                 SendToICS(ics_prefix);
3068                 SendToICS("refresh\n");
3069                 continue;
3070             }
3071
3072             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3073                 ics_clock_paused = TRUE;
3074                 StopClocks();
3075                 continue;
3076             }
3077
3078             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3079                 ics_clock_paused = FALSE;
3080                 StartClocks();
3081                 continue;
3082             }
3083
3084             /* Grab player ratings from the Creating: message.
3085                Note we have to check for the special case when
3086                the ICS inserts things like [white] or [black]. */
3087             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3088                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3089                 /* star_matches:
3090                    0    player 1 name (not necessarily white)
3091                    1    player 1 rating
3092                    2    empty, white, or black (IGNORED)
3093                    3    player 2 name (not necessarily black)
3094                    4    player 2 rating
3095                    
3096                    The names/ratings are sorted out when the game
3097                    actually starts (below).
3098                 */
3099                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3100                 player1Rating = string_to_rating(star_match[1]);
3101                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3102                 player2Rating = string_to_rating(star_match[4]);
3103
3104                 if (appData.debugMode)
3105                   fprintf(debugFP, 
3106                           "Ratings from 'Creating:' %s %d, %s %d\n",
3107                           player1Name, player1Rating,
3108                           player2Name, player2Rating);
3109
3110                 continue;
3111             }
3112             
3113             /* Improved generic start/end-of-game messages */
3114             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3115                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3116                 /* If tkind == 0: */
3117                 /* star_match[0] is the game number */
3118                 /*           [1] is the white player's name */
3119                 /*           [2] is the black player's name */
3120                 /* For end-of-game: */
3121                 /*           [3] is the reason for the game end */
3122                 /*           [4] is a PGN end game-token, preceded by " " */
3123                 /* For start-of-game: */
3124                 /*           [3] begins with "Creating" or "Continuing" */
3125                 /*           [4] is " *" or empty (don't care). */
3126                 int gamenum = atoi(star_match[0]);
3127                 char *whitename, *blackname, *why, *endtoken;
3128                 ChessMove endtype = (ChessMove) 0;
3129
3130                 if (tkind == 0) {
3131                   whitename = star_match[1];
3132                   blackname = star_match[2];
3133                   why = star_match[3];
3134                   endtoken = star_match[4];
3135                 } else {
3136                   whitename = star_match[1];
3137                   blackname = star_match[3];
3138                   why = star_match[5];
3139                   endtoken = star_match[6];
3140                 }
3141
3142                 /* Game start messages */
3143                 if (strncmp(why, "Creating ", 9) == 0 ||
3144                     strncmp(why, "Continuing ", 11) == 0) {
3145                     gs_gamenum = gamenum;
3146                     strcpy(gs_kind, strchr(why, ' ') + 1);
3147                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3148 #if ZIPPY
3149                     if (appData.zippyPlay) {
3150                         ZippyGameStart(whitename, blackname);
3151                     }
3152 #endif /*ZIPPY*/
3153                     continue;
3154                 }
3155
3156                 /* Game end messages */
3157                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3158                     ics_gamenum != gamenum) {
3159                     continue;
3160                 }
3161                 while (endtoken[0] == ' ') endtoken++;
3162                 switch (endtoken[0]) {
3163                   case '*':
3164                   default:
3165                     endtype = GameUnfinished;
3166                     break;
3167                   case '0':
3168                     endtype = BlackWins;
3169                     break;
3170                   case '1':
3171                     if (endtoken[1] == '/')
3172                       endtype = GameIsDrawn;
3173                     else
3174                       endtype = WhiteWins;
3175                     break;
3176                 }
3177                 GameEnds(endtype, why, GE_ICS);
3178 #if ZIPPY
3179                 if (appData.zippyPlay && first.initDone) {
3180                     ZippyGameEnd(endtype, why);
3181                     if (first.pr == NULL) {
3182                       /* Start the next process early so that we'll
3183                          be ready for the next challenge */
3184                       StartChessProgram(&first);
3185                     }
3186                     /* Send "new" early, in case this command takes
3187                        a long time to finish, so that we'll be ready
3188                        for the next challenge. */
3189                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3190                     Reset(TRUE, TRUE);
3191                 }
3192 #endif /*ZIPPY*/
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "Removing game * from observation") ||
3197                 looking_at(buf, &i, "no longer observing game *") ||
3198                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3199                 if (gameMode == IcsObserving &&
3200                     atoi(star_match[0]) == ics_gamenum)
3201                   {
3202                       /* icsEngineAnalyze */
3203                       if (appData.icsEngineAnalyze) {
3204                             ExitAnalyzeMode();
3205                             ModeHighlight();
3206                       }
3207                       StopClocks();
3208                       gameMode = IcsIdle;
3209                       ics_gamenum = -1;
3210                       ics_user_moved = FALSE;
3211                   }
3212                 continue;
3213             }
3214
3215             if (looking_at(buf, &i, "no longer examining game *")) {
3216                 if (gameMode == IcsExamining &&
3217                     atoi(star_match[0]) == ics_gamenum)
3218                   {
3219                       gameMode = IcsIdle;
3220                       ics_gamenum = -1;
3221                       ics_user_moved = FALSE;
3222                   }
3223                 continue;
3224             }
3225
3226             /* Advance leftover_start past any newlines we find,
3227                so only partial lines can get reparsed */
3228             if (looking_at(buf, &i, "\n")) {
3229                 prevColor = curColor;
3230                 if (curColor != ColorNormal) {
3231                     if (oldi > next_out) {
3232                         SendToPlayer(&buf[next_out], oldi - next_out);
3233                         next_out = oldi;
3234                     }
3235                     Colorize(ColorNormal, FALSE);
3236                     curColor = ColorNormal;
3237                 }
3238                 if (started == STARTED_BOARD) {
3239                     started = STARTED_NONE;
3240                     parse[parse_pos] = NULLCHAR;
3241                     ParseBoard12(parse);
3242                     ics_user_moved = 0;
3243
3244                     /* Send premove here */
3245                     if (appData.premove) {
3246                       char str[MSG_SIZ];
3247                       if (currentMove == 0 &&
3248                           gameMode == IcsPlayingWhite &&
3249                           appData.premoveWhite) {
3250                         sprintf(str, "%s\n", appData.premoveWhiteText);
3251                         if (appData.debugMode)
3252                           fprintf(debugFP, "Sending premove:\n");
3253                         SendToICS(str);
3254                       } else if (currentMove == 1 &&
3255                                  gameMode == IcsPlayingBlack &&
3256                                  appData.premoveBlack) {
3257                         sprintf(str, "%s\n", appData.premoveBlackText);
3258                         if (appData.debugMode)
3259                           fprintf(debugFP, "Sending premove:\n");
3260                         SendToICS(str);
3261                       } else if (gotPremove) {
3262                         gotPremove = 0;
3263                         ClearPremoveHighlights();
3264                         if (appData.debugMode)
3265                           fprintf(debugFP, "Sending premove:\n");
3266                           UserMoveEvent(premoveFromX, premoveFromY, 
3267                                         premoveToX, premoveToY, 
3268                                         premovePromoChar);
3269                       }
3270                     }
3271
3272                     /* Usually suppress following prompt */
3273                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3274                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3275                         if (looking_at(buf, &i, "*% ")) {
3276                             savingComment = FALSE;
3277                         }
3278                     }
3279                     next_out = i;
3280                 } else if (started == STARTED_HOLDINGS) {
3281                     int gamenum;
3282                     char new_piece[MSG_SIZ];
3283                     started = STARTED_NONE;
3284                     parse[parse_pos] = NULLCHAR;
3285                     if (appData.debugMode)
3286                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3287                                                         parse, currentMove);
3288                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3289                         gamenum == ics_gamenum) {
3290                         if (gameInfo.variant == VariantNormal) {
3291                           /* [HGM] We seem to switch variant during a game!
3292                            * Presumably no holdings were displayed, so we have
3293                            * to move the position two files to the right to
3294                            * create room for them!
3295                            */
3296                           VariantClass newVariant;
3297                           switch(gameInfo.boardWidth) { // base guess on board width
3298                                 case 9:  newVariant = VariantShogi; break;
3299                                 case 10: newVariant = VariantGreat; break;
3300                                 default: newVariant = VariantCrazyhouse; break;
3301                           }
3302                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3303                           /* Get a move list just to see the header, which
3304                              will tell us whether this is really bug or zh */
3305                           if (ics_getting_history == H_FALSE) {
3306                             ics_getting_history = H_REQUESTED;
3307                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3308                             SendToICS(str);
3309                           }
3310                         }
3311                         new_piece[0] = NULLCHAR;
3312                         sscanf(parse, "game %d white [%s black [%s <- %s",
3313                                &gamenum, white_holding, black_holding,
3314                                new_piece);
3315                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3316                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3317                         /* [HGM] copy holdings to board holdings area */
3318                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3319                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3320                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3321 #if ZIPPY
3322                         if (appData.zippyPlay && first.initDone) {
3323                             ZippyHoldings(white_holding, black_holding,
3324                                           new_piece);
3325                         }
3326 #endif /*ZIPPY*/
3327                         if (tinyLayout || smallLayout) {
3328                             char wh[16], bh[16];
3329                             PackHolding(wh, white_holding);
3330                             PackHolding(bh, black_holding);
3331                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3332                                     gameInfo.white, gameInfo.black);
3333                         } else {
3334                             sprintf(str, "%s [%s] vs. %s [%s]",
3335                                     gameInfo.white, white_holding,
3336                                     gameInfo.black, black_holding);
3337                         }
3338
3339                         DrawPosition(FALSE, boards[currentMove]);
3340                         DisplayTitle(str);
3341                     }
3342                     /* Suppress following prompt */
3343                     if (looking_at(buf, &i, "*% ")) {
3344                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3345                         savingComment = FALSE;
3346                     }
3347                     next_out = i;
3348                 }
3349                 continue;
3350             }
3351
3352             i++;                /* skip unparsed character and loop back */
3353         }
3354         
3355         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3356             started != STARTED_HOLDINGS && i > next_out) {
3357             SendToPlayer(&buf[next_out], i - next_out);
3358             next_out = i;
3359         }
3360         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3361         
3362         leftover_len = buf_len - leftover_start;
3363         /* if buffer ends with something we couldn't parse,
3364            reparse it after appending the next read */
3365         
3366     } else if (count == 0) {
3367         RemoveInputSource(isr);
3368         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3369     } else {
3370         DisplayFatalError(_("Error reading from ICS"), error, 1);
3371     }
3372 }
3373
3374
3375 /* Board style 12 looks like this:
3376    
3377    <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
3378    
3379  * The "<12> " is stripped before it gets to this routine.  The two
3380  * trailing 0's (flip state and clock ticking) are later addition, and
3381  * some chess servers may not have them, or may have only the first.
3382  * Additional trailing fields may be added in the future.  
3383  */
3384
3385 #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"
3386
3387 #define RELATION_OBSERVING_PLAYED    0
3388 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3389 #define RELATION_PLAYING_MYMOVE      1
3390 #define RELATION_PLAYING_NOTMYMOVE  -1
3391 #define RELATION_EXAMINING           2
3392 #define RELATION_ISOLATED_BOARD     -3
3393 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3394
3395 void
3396 ParseBoard12(string)
3397      char *string;
3398
3399     GameMode newGameMode;
3400     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3401     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3402     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3403     char to_play, board_chars[200];
3404     char move_str[500], str[500], elapsed_time[500];
3405     char black[32], white[32];
3406     Board board;
3407     int prevMove = currentMove;
3408     int ticking = 2;
3409     ChessMove moveType;
3410     int fromX, fromY, toX, toY;
3411     char promoChar;
3412     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3413     char *bookHit = NULL; // [HGM] book
3414     Boolean weird = FALSE, reqFlag = FALSE;
3415
3416     fromX = fromY = toX = toY = -1;
3417     
3418     newGame = FALSE;
3419
3420     if (appData.debugMode)
3421       fprintf(debugFP, _("Parsing board: %s\n"), string);
3422
3423     move_str[0] = NULLCHAR;
3424     elapsed_time[0] = NULLCHAR;
3425     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3426         int  i = 0, j;
3427         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3428             if(string[i] == ' ') { ranks++; files = 0; }
3429             else files++;
3430             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3431             i++;
3432         }
3433         for(j = 0; j <i; j++) board_chars[j] = string[j];
3434         board_chars[i] = '\0';
3435         string += i + 1;
3436     }
3437     n = sscanf(string, PATTERN, &to_play, &double_push,
3438                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3439                &gamenum, white, black, &relation, &basetime, &increment,
3440                &white_stren, &black_stren, &white_time, &black_time,
3441                &moveNum, str, elapsed_time, move_str, &ics_flip,
3442                &ticking);
3443
3444     if (n < 21) {
3445         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3446         DisplayError(str, 0);
3447         return;
3448     }
3449
3450     /* Convert the move number to internal form */
3451     moveNum = (moveNum - 1) * 2;
3452     if (to_play == 'B') moveNum++;
3453     if (moveNum >= MAX_MOVES) {
3454       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3455                         0, 1);
3456       return;
3457     }
3458     
3459     switch (relation) {
3460       case RELATION_OBSERVING_PLAYED:
3461       case RELATION_OBSERVING_STATIC:
3462         if (gamenum == -1) {
3463             /* Old ICC buglet */
3464             relation = RELATION_OBSERVING_STATIC;
3465         }
3466         newGameMode = IcsObserving;
3467         break;
3468       case RELATION_PLAYING_MYMOVE:
3469       case RELATION_PLAYING_NOTMYMOVE:
3470         newGameMode =
3471           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3472             IcsPlayingWhite : IcsPlayingBlack;
3473         break;
3474       case RELATION_EXAMINING:
3475         newGameMode = IcsExamining;
3476         break;
3477       case RELATION_ISOLATED_BOARD:
3478       default:
3479         /* Just display this board.  If user was doing something else,
3480            we will forget about it until the next board comes. */ 
3481         newGameMode = IcsIdle;
3482         break;
3483       case RELATION_STARTING_POSITION:
3484         newGameMode = gameMode;
3485         break;
3486     }
3487     
3488     /* Modify behavior for initial board display on move listing
3489        of wild games.
3490        */
3491     switch (ics_getting_history) {
3492       case H_FALSE:
3493       case H_REQUESTED:
3494         break;
3495       case H_GOT_REQ_HEADER:
3496       case H_GOT_UNREQ_HEADER:
3497         /* This is the initial position of the current game */
3498         gamenum = ics_gamenum;
3499         moveNum = 0;            /* old ICS bug workaround */
3500         if (to_play == 'B') {
3501           startedFromSetupPosition = TRUE;
3502           blackPlaysFirst = TRUE;
3503           moveNum = 1;
3504           if (forwardMostMove == 0) forwardMostMove = 1;
3505           if (backwardMostMove == 0) backwardMostMove = 1;
3506           if (currentMove == 0) currentMove = 1;
3507         }
3508         newGameMode = gameMode;
3509         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3510         break;
3511       case H_GOT_UNWANTED_HEADER:
3512         /* This is an initial board that we don't want */
3513         return;
3514       case H_GETTING_MOVES:
3515         /* Should not happen */
3516         DisplayError(_("Error gathering move list: extra board"), 0);
3517         ics_getting_history = H_FALSE;
3518         return;
3519     }
3520
3521    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3522                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3523      /* [HGM] We seem to have switched variant unexpectedly
3524       * Try to guess new variant from board size
3525       */
3526           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3527           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3528           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3529           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3530           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3531           if(!weird) newVariant = VariantNormal;
3532           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3533           /* Get a move list just to see the header, which
3534              will tell us whether this is really bug or zh */
3535           if (ics_getting_history == H_FALSE) {
3536             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3537             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3538             SendToICS(str);
3539           }
3540     }
3541     
3542     /* Take action if this is the first board of a new game, or of a
3543        different game than is currently being displayed.  */
3544     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3545         relation == RELATION_ISOLATED_BOARD) {
3546         
3547         /* Forget the old game and get the history (if any) of the new one */
3548         if (gameMode != BeginningOfGame) {
3549           Reset(TRUE, TRUE);
3550         }
3551         newGame = TRUE;
3552         if (appData.autoRaiseBoard) BoardToTop();
3553         prevMove = -3;
3554         if (gamenum == -1) {
3555             newGameMode = IcsIdle;
3556         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3557                    appData.getMoveList && !reqFlag) {
3558             /* Need to get game history */
3559             ics_getting_history = H_REQUESTED;
3560             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3561             SendToICS(str);
3562         }
3563         
3564         /* Initially flip the board to have black on the bottom if playing
3565            black or if the ICS flip flag is set, but let the user change
3566            it with the Flip View button. */
3567         flipView = appData.autoFlipView ? 
3568           (newGameMode == IcsPlayingBlack) || ics_flip :
3569           appData.flipView;
3570         
3571         /* Done with values from previous mode; copy in new ones */
3572         gameMode = newGameMode;
3573         ModeHighlight();
3574         ics_gamenum = gamenum;
3575         if (gamenum == gs_gamenum) {
3576             int klen = strlen(gs_kind);
3577             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3578             sprintf(str, "ICS %s", gs_kind);
3579             gameInfo.event = StrSave(str);
3580         } else {
3581             gameInfo.event = StrSave("ICS game");
3582         }
3583         gameInfo.site = StrSave(appData.icsHost);
3584         gameInfo.date = PGNDate();
3585         gameInfo.round = StrSave("-");
3586         gameInfo.white = StrSave(white);
3587         gameInfo.black = StrSave(black);
3588         timeControl = basetime * 60 * 1000;
3589         timeControl_2 = 0;
3590         timeIncrement = increment * 1000;
3591         movesPerSession = 0;
3592         gameInfo.timeControl = TimeControlTagValue();
3593         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3594   if (appData.debugMode) {
3595     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3596     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3597     setbuf(debugFP, NULL);
3598   }
3599
3600         gameInfo.outOfBook = NULL;
3601         
3602         /* Do we have the ratings? */
3603         if (strcmp(player1Name, white) == 0 &&
3604             strcmp(player2Name, black) == 0) {
3605             if (appData.debugMode)
3606               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3607                       player1Rating, player2Rating);
3608             gameInfo.whiteRating = player1Rating;
3609             gameInfo.blackRating = player2Rating;
3610         } else if (strcmp(player2Name, white) == 0 &&
3611                    strcmp(player1Name, black) == 0) {
3612             if (appData.debugMode)
3613               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3614                       player2Rating, player1Rating);
3615             gameInfo.whiteRating = player2Rating;
3616             gameInfo.blackRating = player1Rating;
3617         }
3618         player1Name[0] = player2Name[0] = NULLCHAR;
3619
3620         /* Silence shouts if requested */
3621         if (appData.quietPlay &&
3622             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3623             SendToICS(ics_prefix);
3624             SendToICS("set shout 0\n");
3625         }
3626     }
3627     
3628     /* Deal with midgame name changes */
3629     if (!newGame) {
3630         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3631             if (gameInfo.white) free(gameInfo.white);
3632             gameInfo.white = StrSave(white);
3633         }
3634         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3635             if (gameInfo.black) free(gameInfo.black);
3636             gameInfo.black = StrSave(black);
3637         }
3638     }
3639     
3640     /* Throw away game result if anything actually changes in examine mode */
3641     if (gameMode == IcsExamining && !newGame) {
3642         gameInfo.result = GameUnfinished;
3643         if (gameInfo.resultDetails != NULL) {
3644             free(gameInfo.resultDetails);
3645             gameInfo.resultDetails = NULL;
3646         }
3647     }
3648     
3649     /* In pausing && IcsExamining mode, we ignore boards coming
3650        in if they are in a different variation than we are. */
3651     if (pauseExamInvalid) return;
3652     if (pausing && gameMode == IcsExamining) {
3653         if (moveNum <= pauseExamForwardMostMove) {
3654             pauseExamInvalid = TRUE;
3655             forwardMostMove = pauseExamForwardMostMove;
3656             return;
3657         }
3658     }
3659     
3660   if (appData.debugMode) {
3661     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3662   }
3663     /* Parse the board */
3664     for (k = 0; k < ranks; k++) {
3665       for (j = 0; j < files; j++)
3666         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3667       if(gameInfo.holdingsWidth > 1) {
3668            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3669            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3670       }
3671     }
3672     CopyBoard(boards[moveNum], board);
3673     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3674     if (moveNum == 0) {
3675         startedFromSetupPosition =
3676           !CompareBoards(board, initialPosition);
3677         if(startedFromSetupPosition)
3678             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3679     }
3680
3681     /* [HGM] Set castling rights. Take the outermost Rooks,
3682        to make it also work for FRC opening positions. Note that board12
3683        is really defective for later FRC positions, as it has no way to
3684        indicate which Rook can castle if they are on the same side of King.
3685        For the initial position we grant rights to the outermost Rooks,
3686        and remember thos rights, and we then copy them on positions
3687        later in an FRC game. This means WB might not recognize castlings with
3688        Rooks that have moved back to their original position as illegal,
3689        but in ICS mode that is not its job anyway.
3690     */
3691     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3692     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3693
3694         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3695             if(board[0][i] == WhiteRook) j = i;
3696         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3697         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3698             if(board[0][i] == WhiteRook) j = i;
3699         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3700         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3701             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3702         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3703         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3704             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3706
3707         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3708         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3709             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3710         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3711             if(board[BOARD_HEIGHT-1][k] == bKing)
3712                 initialRights[5] = castlingRights[moveNum][5] = k;
3713         if(gameInfo.variant == VariantTwoKings) {
3714             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3715             if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3716             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3717         }
3718     } else { int r;
3719         r = castlingRights[moveNum][0] = initialRights[0];
3720         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3721         r = castlingRights[moveNum][1] = initialRights[1];
3722         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3723         r = castlingRights[moveNum][3] = initialRights[3];
3724         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3725         r = castlingRights[moveNum][4] = initialRights[4];
3726         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3727         /* wildcastle kludge: always assume King has rights */
3728         r = castlingRights[moveNum][2] = initialRights[2];
3729         r = castlingRights[moveNum][5] = initialRights[5];
3730     }
3731     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3732     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3733
3734     
3735     if (ics_getting_history == H_GOT_REQ_HEADER ||
3736         ics_getting_history == H_GOT_UNREQ_HEADER) {
3737         /* This was an initial position from a move list, not
3738            the current position */
3739         return;
3740     }
3741     
3742     /* Update currentMove and known move number limits */
3743     newMove = newGame || moveNum > forwardMostMove;
3744
3745     if (newGame) {
3746         forwardMostMove = backwardMostMove = currentMove = moveNum;
3747         if (gameMode == IcsExamining && moveNum == 0) {
3748           /* Workaround for ICS limitation: we are not told the wild
3749              type when starting to examine a game.  But if we ask for
3750              the move list, the move list header will tell us */
3751             ics_getting_history = H_REQUESTED;
3752             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3753             SendToICS(str);
3754         }
3755     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3756                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3757 #if ZIPPY
3758         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3759         /* [HGM] applied this also to an engine that is silently watching        */
3760         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3761             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3762             gameInfo.variant == currentlyInitializedVariant) {
3763           takeback = forwardMostMove - moveNum;
3764           for (i = 0; i < takeback; i++) {
3765             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3766             SendToProgram("undo\n", &first);
3767           }
3768         }
3769 #endif
3770
3771         forwardMostMove = moveNum;
3772         if (!pausing || currentMove > forwardMostMove)
3773           currentMove = forwardMostMove;
3774     } else {
3775         /* New part of history that is not contiguous with old part */ 
3776         if (pausing && gameMode == IcsExamining) {
3777             pauseExamInvalid = TRUE;
3778             forwardMostMove = pauseExamForwardMostMove;
3779             return;
3780         }
3781         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3782 #if ZIPPY
3783             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3784                 // [HGM] when we will receive the move list we now request, it will be
3785                 // fed to the engine from the first move on. So if the engine is not
3786                 // in the initial position now, bring it there.
3787                 InitChessProgram(&first, 0);
3788             }
3789 #endif
3790             ics_getting_history = H_REQUESTED;
3791             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3792             SendToICS(str);
3793         }
3794         forwardMostMove = backwardMostMove = currentMove = moveNum;
3795     }
3796     
3797     /* Update the clocks */
3798     if (strchr(elapsed_time, '.')) {
3799       /* Time is in ms */
3800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3802     } else {
3803       /* Time is in seconds */
3804       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3805       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3806     }
3807       
3808
3809 #if ZIPPY
3810     if (appData.zippyPlay && newGame &&
3811         gameMode != IcsObserving && gameMode != IcsIdle &&
3812         gameMode != IcsExamining)
3813       ZippyFirstBoard(moveNum, basetime, increment);
3814 #endif
3815     
3816     /* Put the move on the move list, first converting
3817        to canonical algebraic form. */
3818     if (moveNum > 0) {
3819   if (appData.debugMode) {
3820     if (appData.debugMode) { int f = forwardMostMove;
3821         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3822                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3823     }
3824     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3825     fprintf(debugFP, "moveNum = %d\n", moveNum);
3826     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3827     setbuf(debugFP, NULL);
3828   }
3829         if (moveNum <= backwardMostMove) {
3830             /* We don't know what the board looked like before
3831                this move.  Punt. */
3832             strcpy(parseList[moveNum - 1], move_str);
3833             strcat(parseList[moveNum - 1], " ");
3834             strcat(parseList[moveNum - 1], elapsed_time);
3835             moveList[moveNum - 1][0] = NULLCHAR;
3836         } else if (strcmp(move_str, "none") == 0) {
3837             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3838             /* Again, we don't know what the board looked like;
3839                this is really the start of the game. */
3840             parseList[moveNum - 1][0] = NULLCHAR;
3841             moveList[moveNum - 1][0] = NULLCHAR;
3842             backwardMostMove = moveNum;
3843             startedFromSetupPosition = TRUE;
3844             fromX = fromY = toX = toY = -1;
3845         } else {
3846           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3847           //                 So we parse the long-algebraic move string in stead of the SAN move
3848           int valid; char buf[MSG_SIZ], *prom;
3849
3850           // str looks something like "Q/a1-a2"; kill the slash
3851           if(str[1] == '/') 
3852                 sprintf(buf, "%c%s", str[0], str+2);
3853           else  strcpy(buf, str); // might be castling
3854           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3855                 strcat(buf, prom); // long move lacks promo specification!
3856           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3857                 if(appData.debugMode) 
3858                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3859                 strcpy(move_str, buf);
3860           }
3861           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3862                                 &fromX, &fromY, &toX, &toY, &promoChar)
3863                || ParseOneMove(buf, moveNum - 1, &moveType,
3864                                 &fromX, &fromY, &toX, &toY, &promoChar);
3865           // end of long SAN patch
3866           if (valid) {
3867             (void) CoordsToAlgebraic(boards[moveNum - 1],
3868                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3869                                      fromY, fromX, toY, toX, promoChar,
3870                                      parseList[moveNum-1]);
3871             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3872                              castlingRights[moveNum]) ) {
3873               case MT_NONE:
3874               case MT_STALEMATE:
3875               default:
3876                 break;
3877               case MT_CHECK:
3878                 if(gameInfo.variant != VariantShogi)
3879                     strcat(parseList[moveNum - 1], "+");
3880                 break;
3881               case MT_CHECKMATE:
3882               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3883                 strcat(parseList[moveNum - 1], "#");
3884                 break;
3885             }
3886             strcat(parseList[moveNum - 1], " ");
3887             strcat(parseList[moveNum - 1], elapsed_time);
3888             /* currentMoveString is set as a side-effect of ParseOneMove */
3889             strcpy(moveList[moveNum - 1], currentMoveString);
3890             strcat(moveList[moveNum - 1], "\n");
3891           } else {
3892             /* Move from ICS was illegal!?  Punt. */
3893   if (appData.debugMode) {
3894     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3895     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3896   }
3897             strcpy(parseList[moveNum - 1], move_str);
3898             strcat(parseList[moveNum - 1], " ");
3899             strcat(parseList[moveNum - 1], elapsed_time);
3900             moveList[moveNum - 1][0] = NULLCHAR;
3901             fromX = fromY = toX = toY = -1;
3902           }
3903         }
3904   if (appData.debugMode) {
3905     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3906     setbuf(debugFP, NULL);
3907   }
3908
3909 #if ZIPPY
3910         /* Send move to chess program (BEFORE animating it). */
3911         if (appData.zippyPlay && !newGame && newMove && 
3912            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3913
3914             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3915                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3916                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3917                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3918                             move_str);
3919                     DisplayError(str, 0);
3920                 } else {
3921                     if (first.sendTime) {
3922                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3923                     }
3924                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3925                     if (firstMove && !bookHit) {
3926                         firstMove = FALSE;
3927                         if (first.useColors) {
3928                           SendToProgram(gameMode == IcsPlayingWhite ?
3929                                         "white\ngo\n" :
3930                                         "black\ngo\n", &first);
3931                         } else {
3932                           SendToProgram("go\n", &first);
3933                         }
3934                         first.maybeThinking = TRUE;
3935                     }
3936                 }
3937             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3938               if (moveList[moveNum - 1][0] == NULLCHAR) {
3939                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3940                 DisplayError(str, 0);
3941               } else {
3942                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3943                 SendMoveToProgram(moveNum - 1, &first);
3944               }
3945             }
3946         }
3947 #endif
3948     }
3949
3950     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3951         /* If move comes from a remote source, animate it.  If it
3952            isn't remote, it will have already been animated. */
3953         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3954             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3955         }
3956         if (!pausing && appData.highlightLastMove) {
3957             SetHighlights(fromX, fromY, toX, toY);
3958         }
3959     }
3960     
3961     /* Start the clocks */
3962     whiteFlag = blackFlag = FALSE;
3963     appData.clockMode = !(basetime == 0 && increment == 0);
3964     if (ticking == 0) {
3965       ics_clock_paused = TRUE;
3966       StopClocks();
3967     } else if (ticking == 1) {
3968       ics_clock_paused = FALSE;
3969     }
3970     if (gameMode == IcsIdle ||
3971         relation == RELATION_OBSERVING_STATIC ||
3972         relation == RELATION_EXAMINING ||
3973         ics_clock_paused)
3974       DisplayBothClocks();
3975     else
3976       StartClocks();
3977     
3978     /* Display opponents and material strengths */
3979     if (gameInfo.variant != VariantBughouse &&
3980         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3981         if (tinyLayout || smallLayout) {
3982             if(gameInfo.variant == VariantNormal)
3983                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3984                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3985                     basetime, increment);
3986             else
3987                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3988                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3989                     basetime, increment, (int) gameInfo.variant);
3990         } else {
3991             if(gameInfo.variant == VariantNormal)
3992                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3994                     basetime, increment);
3995             else
3996                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3998                     basetime, increment, VariantName(gameInfo.variant));
3999         }
4000         DisplayTitle(str);
4001   if (appData.debugMode) {
4002     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4003   }
4004     }
4005
4006    
4007     /* Display the board */
4008     if (!pausing && !appData.noGUI) {
4009       
4010       if (appData.premove)
4011           if (!gotPremove || 
4012              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4013              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4014               ClearPremoveHighlights();
4015
4016       DrawPosition(FALSE, boards[currentMove]);
4017       DisplayMove(moveNum - 1);
4018       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4019             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4020               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4021         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4022       }
4023     }
4024
4025     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4026 #if ZIPPY
4027     if(bookHit) { // [HGM] book: simulate book reply
4028         static char bookMove[MSG_SIZ]; // a bit generous?
4029
4030         programStats.nodes = programStats.depth = programStats.time = 
4031         programStats.score = programStats.got_only_move = 0;
4032         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4033
4034         strcpy(bookMove, "move ");
4035         strcat(bookMove, bookHit);
4036         HandleMachineMove(bookMove, &first);
4037     }
4038 #endif
4039 }
4040
4041 void
4042 GetMoveListEvent()
4043 {
4044     char buf[MSG_SIZ];
4045     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4046         ics_getting_history = H_REQUESTED;
4047         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4048         SendToICS(buf);
4049     }
4050 }
4051
4052 void
4053 AnalysisPeriodicEvent(force)
4054      int force;
4055 {
4056     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4057          && !force) || !appData.periodicUpdates)
4058       return;
4059
4060     /* Send . command to Crafty to collect stats */
4061     SendToProgram(".\n", &first);
4062
4063     /* Don't send another until we get a response (this makes
4064        us stop sending to old Crafty's which don't understand
4065        the "." command (sending illegal cmds resets node count & time,
4066        which looks bad)) */
4067     programStats.ok_to_send = 0;
4068 }
4069
4070 void ics_update_width(new_width)
4071         int new_width;
4072 {
4073         ics_printf("set width %d\n", new_width);
4074 }
4075
4076 void
4077 SendMoveToProgram(moveNum, cps)
4078      int moveNum;
4079      ChessProgramState *cps;
4080 {
4081     char buf[MSG_SIZ];
4082
4083     if (cps->useUsermove) {
4084       SendToProgram("usermove ", cps);
4085     }
4086     if (cps->useSAN) {
4087       char *space;
4088       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4089         int len = space - parseList[moveNum];
4090         memcpy(buf, parseList[moveNum], len);
4091         buf[len++] = '\n';
4092         buf[len] = NULLCHAR;
4093       } else {
4094         sprintf(buf, "%s\n", parseList[moveNum]);
4095       }
4096       SendToProgram(buf, cps);
4097     } else {
4098       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4099         AlphaRank(moveList[moveNum], 4);
4100         SendToProgram(moveList[moveNum], cps);
4101         AlphaRank(moveList[moveNum], 4); // and back
4102       } else
4103       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4104        * the engine. It would be nice to have a better way to identify castle 
4105        * moves here. */
4106       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4107                                                                          && cps->useOOCastle) {
4108         int fromX = moveList[moveNum][0] - AAA; 
4109         int fromY = moveList[moveNum][1] - ONE;
4110         int toX = moveList[moveNum][2] - AAA; 
4111         int toY = moveList[moveNum][3] - ONE;
4112         if((boards[moveNum][fromY][fromX] == WhiteKing 
4113             && boards[moveNum][toY][toX] == WhiteRook)
4114            || (boards[moveNum][fromY][fromX] == BlackKing 
4115                && boards[moveNum][toY][toX] == BlackRook)) {
4116           if(toX > fromX) SendToProgram("O-O\n", cps);
4117           else SendToProgram("O-O-O\n", cps);
4118         }
4119         else SendToProgram(moveList[moveNum], cps);
4120       }
4121       else SendToProgram(moveList[moveNum], cps);
4122       /* End of additions by Tord */
4123     }
4124
4125     /* [HGM] setting up the opening has brought engine in force mode! */
4126     /*       Send 'go' if we are in a mode where machine should play. */
4127     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4128         (gameMode == TwoMachinesPlay   ||
4129 #ifdef ZIPPY
4130          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4131 #endif
4132          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4133         SendToProgram("go\n", cps);
4134   if (appData.debugMode) {
4135     fprintf(debugFP, "(extra)\n");
4136   }
4137     }
4138     setboardSpoiledMachineBlack = 0;
4139 }
4140
4141 void
4142 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4143      ChessMove moveType;
4144      int fromX, fromY, toX, toY;
4145 {
4146     char user_move[MSG_SIZ];
4147
4148     switch (moveType) {
4149       default:
4150         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4151                 (int)moveType, fromX, fromY, toX, toY);
4152         DisplayError(user_move + strlen("say "), 0);
4153         break;
4154       case WhiteKingSideCastle:
4155       case BlackKingSideCastle:
4156       case WhiteQueenSideCastleWild:
4157       case BlackQueenSideCastleWild:
4158       /* PUSH Fabien */
4159       case WhiteHSideCastleFR:
4160       case BlackHSideCastleFR:
4161       /* POP Fabien */
4162         sprintf(user_move, "o-o\n");
4163         break;
4164       case WhiteQueenSideCastle:
4165       case BlackQueenSideCastle:
4166       case WhiteKingSideCastleWild:
4167       case BlackKingSideCastleWild:
4168       /* PUSH Fabien */
4169       case WhiteASideCastleFR:
4170       case BlackASideCastleFR:
4171       /* POP Fabien */
4172         sprintf(user_move, "o-o-o\n");
4173         break;
4174       case WhitePromotionQueen:
4175       case BlackPromotionQueen:
4176       case WhitePromotionRook:
4177       case BlackPromotionRook:
4178       case WhitePromotionBishop:
4179       case BlackPromotionBishop:
4180       case WhitePromotionKnight:
4181       case BlackPromotionKnight:
4182       case WhitePromotionKing:
4183       case BlackPromotionKing:
4184       case WhitePromotionChancellor:
4185       case BlackPromotionChancellor:
4186       case WhitePromotionArchbishop:
4187       case BlackPromotionArchbishop:
4188         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4189             sprintf(user_move, "%c%c%c%c=%c\n",
4190                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191                 PieceToChar(WhiteFerz));
4192         else if(gameInfo.variant == VariantGreat)
4193             sprintf(user_move, "%c%c%c%c=%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195                 PieceToChar(WhiteMan));
4196         else
4197             sprintf(user_move, "%c%c%c%c=%c\n",
4198                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4199                 PieceToChar(PromoPiece(moveType)));
4200         break;
4201       case WhiteDrop:
4202       case BlackDrop:
4203         sprintf(user_move, "%c@%c%c\n",
4204                 ToUpper(PieceToChar((ChessSquare) fromX)),
4205                 AAA + toX, ONE + toY);
4206         break;
4207       case NormalMove:
4208       case WhiteCapturesEnPassant:
4209       case BlackCapturesEnPassant:
4210       case IllegalMove:  /* could be a variant we don't quite understand */
4211         sprintf(user_move, "%c%c%c%c\n",
4212                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4213         break;
4214     }
4215     SendToICS(user_move);
4216     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4217         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4218 }
4219
4220 void
4221 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4222      int rf, ff, rt, ft;
4223      char promoChar;
4224      char move[7];
4225 {
4226     if (rf == DROP_RANK) {
4227         sprintf(move, "%c@%c%c\n",
4228                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4229     } else {
4230         if (promoChar == 'x' || promoChar == NULLCHAR) {
4231             sprintf(move, "%c%c%c%c\n",
4232                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4233         } else {
4234             sprintf(move, "%c%c%c%c%c\n",
4235                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4236         }
4237     }
4238 }
4239
4240 void
4241 ProcessICSInitScript(f)
4242      FILE *f;
4243 {
4244     char buf[MSG_SIZ];
4245
4246     while (fgets(buf, MSG_SIZ, f)) {
4247         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4248     }
4249
4250     fclose(f);
4251 }
4252
4253
4254 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4255 void
4256 AlphaRank(char *move, int n)
4257 {
4258 //    char *p = move, c; int x, y;
4259
4260     if (appData.debugMode) {
4261         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4262     }
4263
4264     if(move[1]=='*' && 
4265        move[2]>='0' && move[2]<='9' &&
4266        move[3]>='a' && move[3]<='x'    ) {
4267         move[1] = '@';
4268         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4269         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4270     } else
4271     if(move[0]>='0' && move[0]<='9' &&
4272        move[1]>='a' && move[1]<='x' &&
4273        move[2]>='0' && move[2]<='9' &&
4274        move[3]>='a' && move[3]<='x'    ) {
4275         /* input move, Shogi -> normal */
4276         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4277         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4278         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4279         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4280     } else
4281     if(move[1]=='@' &&
4282        move[3]>='0' && move[3]<='9' &&
4283        move[2]>='a' && move[2]<='x'    ) {
4284         move[1] = '*';
4285         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4286         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4287     } else
4288     if(
4289        move[0]>='a' && move[0]<='x' &&
4290        move[3]>='0' && move[3]<='9' &&
4291        move[2]>='a' && move[2]<='x'    ) {
4292          /* output move, normal -> Shogi */
4293         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4294         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4295         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4296         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4297         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4298     }
4299     if (appData.debugMode) {
4300         fprintf(debugFP, "   out = '%s'\n", move);
4301     }
4302 }
4303
4304 /* Parser for moves from gnuchess, ICS, or user typein box */
4305 Boolean
4306 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4307      char *move;
4308      int moveNum;
4309      ChessMove *moveType;
4310      int *fromX, *fromY, *toX, *toY;
4311      char *promoChar;
4312 {       
4313     if (appData.debugMode) {
4314         fprintf(debugFP, "move to parse: %s\n", move);
4315     }
4316     *moveType = yylexstr(moveNum, move);
4317
4318     switch (*moveType) {
4319       case WhitePromotionChancellor:
4320       case BlackPromotionChancellor:
4321       case WhitePromotionArchbishop:
4322       case BlackPromotionArchbishop:
4323       case WhitePromotionQueen:
4324       case BlackPromotionQueen:
4325       case WhitePromotionRook:
4326       case BlackPromotionRook:
4327       case WhitePromotionBishop:
4328       case BlackPromotionBishop:
4329       case WhitePromotionKnight:
4330       case BlackPromotionKnight:
4331       case WhitePromotionKing:
4332       case BlackPromotionKing:
4333       case NormalMove:
4334       case WhiteCapturesEnPassant:
4335       case BlackCapturesEnPassant:
4336       case WhiteKingSideCastle:
4337       case WhiteQueenSideCastle:
4338       case BlackKingSideCastle:
4339       case BlackQueenSideCastle:
4340       case WhiteKingSideCastleWild:
4341       case WhiteQueenSideCastleWild:
4342       case BlackKingSideCastleWild:
4343       case BlackQueenSideCastleWild:
4344       /* Code added by Tord: */
4345       case WhiteHSideCastleFR:
4346       case WhiteASideCastleFR:
4347       case BlackHSideCastleFR:
4348       case BlackASideCastleFR:
4349       /* End of code added by Tord */
4350       case IllegalMove:         /* bug or odd chess variant */
4351         *fromX = currentMoveString[0] - AAA;
4352         *fromY = currentMoveString[1] - ONE;
4353         *toX = currentMoveString[2] - AAA;
4354         *toY = currentMoveString[3] - ONE;
4355         *promoChar = currentMoveString[4];
4356         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4357             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4358     if (appData.debugMode) {
4359         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4360     }
4361             *fromX = *fromY = *toX = *toY = 0;
4362             return FALSE;
4363         }
4364         if (appData.testLegality) {
4365           return (*moveType != IllegalMove);
4366         } else {
4367           return !(*fromX == *toX && *fromY == *toY);
4368         }
4369
4370       case WhiteDrop:
4371       case BlackDrop:
4372         *fromX = *moveType == WhiteDrop ?
4373           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4374           (int) CharToPiece(ToLower(currentMoveString[0]));
4375         *fromY = DROP_RANK;
4376         *toX = currentMoveString[2] - AAA;
4377         *toY = currentMoveString[3] - ONE;
4378         *promoChar = NULLCHAR;
4379         return TRUE;
4380
4381       case AmbiguousMove:
4382       case ImpossibleMove:
4383       case (ChessMove) 0:       /* end of file */
4384       case ElapsedTime:
4385       case Comment:
4386       case PGNTag:
4387       case NAG:
4388       case WhiteWins:
4389       case BlackWins:
4390       case GameIsDrawn:
4391       default:
4392     if (appData.debugMode) {
4393         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4394     }
4395         /* bug? */
4396         *fromX = *fromY = *toX = *toY = 0;
4397         *promoChar = NULLCHAR;
4398         return FALSE;
4399     }
4400 }
4401
4402 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4403 // All positions will have equal probability, but the current method will not provide a unique
4404 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4405 #define DARK 1
4406 #define LITE 2
4407 #define ANY 3
4408
4409 int squaresLeft[4];
4410 int piecesLeft[(int)BlackPawn];
4411 int seed, nrOfShuffles;
4412
4413 void GetPositionNumber()
4414 {       // sets global variable seed
4415         int i;
4416
4417         seed = appData.defaultFrcPosition;
4418         if(seed < 0) { // randomize based on time for negative FRC position numbers
4419                 for(i=0; i<50; i++) seed += random();
4420                 seed = random() ^ random() >> 8 ^ random() << 8;
4421                 if(seed<0) seed = -seed;
4422         }
4423 }
4424
4425 int put(Board board, int pieceType, int rank, int n, int shade)
4426 // put the piece on the (n-1)-th empty squares of the given shade
4427 {
4428         int i;
4429
4430         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4431                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4432                         board[rank][i] = (ChessSquare) pieceType;
4433                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4434                         squaresLeft[ANY]--;
4435                         piecesLeft[pieceType]--; 
4436                         return i;
4437                 }
4438         }
4439         return -1;
4440 }
4441
4442
4443 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4444 // calculate where the next piece goes, (any empty square), and put it there
4445 {
4446         int i;
4447
4448         i = seed % squaresLeft[shade];
4449         nrOfShuffles *= squaresLeft[shade];
4450         seed /= squaresLeft[shade];
4451         put(board, pieceType, rank, i, shade);
4452 }
4453
4454 void AddTwoPieces(Board board, int pieceType, int rank)
4455 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4456 {
4457         int i, n=squaresLeft[ANY], j=n-1, k;
4458
4459         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4460         i = seed % k;  // pick one
4461         nrOfShuffles *= k;
4462         seed /= k;
4463         while(i >= j) i -= j--;
4464         j = n - 1 - j; i += j;
4465         put(board, pieceType, rank, j, ANY);
4466         put(board, pieceType, rank, i, ANY);
4467 }
4468
4469 void SetUpShuffle(Board board, int number)
4470 {
4471         int i, p, first=1;
4472
4473         GetPositionNumber(); nrOfShuffles = 1;
4474
4475         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4476         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4477         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4478
4479         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4480
4481         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4482             p = (int) board[0][i];
4483             if(p < (int) BlackPawn) piecesLeft[p] ++;
4484             board[0][i] = EmptySquare;
4485         }
4486
4487         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4488             // shuffles restricted to allow normal castling put KRR first
4489             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4490                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4491             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4492                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4493             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4494                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4495             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4496                 put(board, WhiteRook, 0, 0, ANY);
4497             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4498         }
4499
4500         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4501             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4502             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4503                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4504                 while(piecesLeft[p] >= 2) {
4505                     AddOnePiece(board, p, 0, LITE);
4506                     AddOnePiece(board, p, 0, DARK);
4507                 }
4508                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4509             }
4510
4511         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4512             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4513             // but we leave King and Rooks for last, to possibly obey FRC restriction
4514             if(p == (int)WhiteRook) continue;
4515             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4516             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4517         }
4518
4519         // now everything is placed, except perhaps King (Unicorn) and Rooks
4520
4521         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4522             // Last King gets castling rights
4523             while(piecesLeft[(int)WhiteUnicorn]) {
4524                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4525                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4526             }
4527
4528             while(piecesLeft[(int)WhiteKing]) {
4529                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4530                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4531             }
4532
4533
4534         } else {
4535             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4536             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4537         }
4538
4539         // Only Rooks can be left; simply place them all
4540         while(piecesLeft[(int)WhiteRook]) {
4541                 i = put(board, WhiteRook, 0, 0, ANY);
4542                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4543                         if(first) {
4544                                 first=0;
4545                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4546                         }
4547                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4548                 }
4549         }
4550         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4551             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4552         }
4553
4554         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4555 }
4556
4557 int SetCharTable( char *table, const char * map )
4558 /* [HGM] moved here from winboard.c because of its general usefulness */
4559 /*       Basically a safe strcpy that uses the last character as King */
4560 {
4561     int result = FALSE; int NrPieces;
4562
4563     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4564                     && NrPieces >= 12 && !(NrPieces&1)) {
4565         int i; /* [HGM] Accept even length from 12 to 34 */
4566
4567         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4568         for( i=0; i<NrPieces/2-1; i++ ) {
4569             table[i] = map[i];
4570             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4571         }
4572         table[(int) WhiteKing]  = map[NrPieces/2-1];
4573         table[(int) BlackKing]  = map[NrPieces-1];
4574
4575         result = TRUE;
4576     }
4577
4578     return result;
4579 }
4580
4581 void Prelude(Board board)
4582 {       // [HGM] superchess: random selection of exo-pieces
4583         int i, j, k; ChessSquare p; 
4584         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4585
4586         GetPositionNumber(); // use FRC position number
4587
4588         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4589             SetCharTable(pieceToChar, appData.pieceToCharTable);
4590             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4591                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4592         }
4593
4594         j = seed%4;                 seed /= 4; 
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 >= j); seed /= 3; 
4599         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3; 
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%2 + (seed%2 >= j); seed /= 2; 
4607         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4608         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4609         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4610         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4611         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4612         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4613         put(board, exoPieces[0],    0, 0, ANY);
4614         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4615 }
4616
4617 void
4618 InitPosition(redraw)
4619      int redraw;
4620 {
4621     ChessSquare (* pieces)[BOARD_SIZE];
4622     int i, j, pawnRow, overrule,
4623     oldx = gameInfo.boardWidth,
4624     oldy = gameInfo.boardHeight,
4625     oldh = gameInfo.holdingsWidth,
4626     oldv = gameInfo.variant;
4627
4628     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4629
4630     /* [AS] Initialize pv info list [HGM] and game status */
4631     {
4632         for( i=0; i<MAX_MOVES; i++ ) {
4633             pvInfoList[i].depth = 0;
4634             epStatus[i]=EP_NONE;
4635             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4636         }
4637
4638         initialRulePlies = 0; /* 50-move counter start */
4639
4640         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4641         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4642     }
4643
4644     
4645     /* [HGM] logic here is completely changed. In stead of full positions */
4646     /* the initialized data only consist of the two backranks. The switch */
4647     /* selects which one we will use, which is than copied to the Board   */
4648     /* initialPosition, which for the rest is initialized by Pawns and    */
4649     /* empty squares. This initial position is then copied to boards[0],  */
4650     /* possibly after shuffling, so that it remains available.            */
4651
4652     gameInfo.holdingsWidth = 0; /* default board sizes */
4653     gameInfo.boardWidth    = 8;
4654     gameInfo.boardHeight   = 8;
4655     gameInfo.holdingsSize  = 0;
4656     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4657     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4658     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4659
4660     switch (gameInfo.variant) {
4661     case VariantFischeRandom:
4662       shuffleOpenings = TRUE;
4663     default:
4664       pieces = FIDEArray;
4665       break;
4666     case VariantShatranj:
4667       pieces = ShatranjArray;
4668       nrCastlingRights = 0;
4669       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4670       break;
4671     case VariantTwoKings:
4672       pieces = twoKingsArray;
4673       break;
4674     case VariantCapaRandom:
4675       shuffleOpenings = TRUE;
4676     case VariantCapablanca:
4677       pieces = CapablancaArray;
4678       gameInfo.boardWidth = 10;
4679       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4680       break;
4681     case VariantGothic:
4682       pieces = GothicArray;
4683       gameInfo.boardWidth = 10;
4684       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4685       break;
4686     case VariantJanus:
4687       pieces = JanusArray;
4688       gameInfo.boardWidth = 10;
4689       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4690       nrCastlingRights = 6;
4691         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4692         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4693         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4694         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4695         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4696         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4697       break;
4698     case VariantFalcon:
4699       pieces = FalconArray;
4700       gameInfo.boardWidth = 10;
4701       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4702       break;
4703     case VariantXiangqi:
4704       pieces = XiangqiArray;
4705       gameInfo.boardWidth  = 9;
4706       gameInfo.boardHeight = 10;
4707       nrCastlingRights = 0;
4708       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4709       break;
4710     case VariantShogi:
4711       pieces = ShogiArray;
4712       gameInfo.boardWidth  = 9;
4713       gameInfo.boardHeight = 9;
4714       gameInfo.holdingsSize = 7;
4715       nrCastlingRights = 0;
4716       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4717       break;
4718     case VariantCourier:
4719       pieces = CourierArray;
4720       gameInfo.boardWidth  = 12;
4721       nrCastlingRights = 0;
4722       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4723       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4724       break;
4725     case VariantKnightmate:
4726       pieces = KnightmateArray;
4727       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4728       break;
4729     case VariantFairy:
4730       pieces = fairyArray;
4731       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4732       break;
4733     case VariantGreat:
4734       pieces = GreatArray;
4735       gameInfo.boardWidth = 10;
4736       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4737       gameInfo.holdingsSize = 8;
4738       break;
4739     case VariantSuper:
4740       pieces = FIDEArray;
4741       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4742       gameInfo.holdingsSize = 8;
4743       startedFromSetupPosition = TRUE;
4744       break;
4745     case VariantCrazyhouse:
4746     case VariantBughouse:
4747       pieces = FIDEArray;
4748       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4749       gameInfo.holdingsSize = 5;
4750       break;
4751     case VariantWildCastle:
4752       pieces = FIDEArray;
4753       /* !!?shuffle with kings guaranteed to be on d or e file */
4754       shuffleOpenings = 1;
4755       break;
4756     case VariantNoCastle:
4757       pieces = FIDEArray;
4758       nrCastlingRights = 0;
4759       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4760       /* !!?unconstrained back-rank shuffle */
4761       shuffleOpenings = 1;
4762       break;
4763     }
4764
4765     overrule = 0;
4766     if(appData.NrFiles >= 0) {
4767         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4768         gameInfo.boardWidth = appData.NrFiles;
4769     }
4770     if(appData.NrRanks >= 0) {
4771         gameInfo.boardHeight = appData.NrRanks;
4772     }
4773     if(appData.holdingsSize >= 0) {
4774         i = appData.holdingsSize;
4775         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4776         gameInfo.holdingsSize = i;
4777     }
4778     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4779     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4780         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4781
4782     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4783     if(pawnRow < 1) pawnRow = 1;
4784
4785     /* User pieceToChar list overrules defaults */
4786     if(appData.pieceToCharTable != NULL)
4787         SetCharTable(pieceToChar, appData.pieceToCharTable);
4788
4789     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4790
4791         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4792             s = (ChessSquare) 0; /* account holding counts in guard band */
4793         for( i=0; i<BOARD_HEIGHT; i++ )
4794             initialPosition[i][j] = s;
4795
4796         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4797         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4798         initialPosition[pawnRow][j] = WhitePawn;
4799         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4800         if(gameInfo.variant == VariantXiangqi) {
4801             if(j&1) {
4802                 initialPosition[pawnRow][j] = 
4803                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4804                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4805                    initialPosition[2][j] = WhiteCannon;
4806                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4807                 }
4808             }
4809         }
4810         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4811     }
4812     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4813
4814             j=BOARD_LEFT+1;
4815             initialPosition[1][j] = WhiteBishop;
4816             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4817             j=BOARD_RGHT-2;
4818             initialPosition[1][j] = WhiteRook;
4819             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4820     }
4821
4822     if( nrCastlingRights == -1) {
4823         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4824         /*       This sets default castling rights from none to normal corners   */
4825         /* Variants with other castling rights must set them themselves above    */
4826         nrCastlingRights = 6;
4827        
4828         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4829         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4830         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4831         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4832         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4833         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4834      }
4835
4836      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4837      if(gameInfo.variant == VariantGreat) { // promotion commoners
4838         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4839         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4840         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4841         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4842      }
4843   if (appData.debugMode) {
4844     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4845   }
4846     if(shuffleOpenings) {
4847         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4848         startedFromSetupPosition = TRUE;
4849     }
4850     if(startedFromPositionFile) {
4851       /* [HGM] loadPos: use PositionFile for every new game */
4852       CopyBoard(initialPosition, filePosition);
4853       for(i=0; i<nrCastlingRights; i++)
4854           castlingRights[0][i] = initialRights[i] = fileRights[i];
4855       startedFromSetupPosition = TRUE;
4856     }
4857
4858     CopyBoard(boards[0], initialPosition);
4859
4860     if(oldx != gameInfo.boardWidth ||
4861        oldy != gameInfo.boardHeight ||
4862        oldh != gameInfo.holdingsWidth
4863 #ifdef GOTHIC
4864        || oldv == VariantGothic ||        // For licensing popups
4865        gameInfo.variant == VariantGothic
4866 #endif
4867 #ifdef FALCON
4868        || oldv == VariantFalcon ||
4869        gameInfo.variant == VariantFalcon
4870 #endif
4871                                          )
4872             InitDrawingSizes(-2 ,0);
4873
4874     if (redraw)
4875       DrawPosition(TRUE, boards[currentMove]);
4876 }
4877
4878 void
4879 SendBoard(cps, moveNum)
4880      ChessProgramState *cps;
4881      int moveNum;
4882 {
4883     char message[MSG_SIZ];
4884     
4885     if (cps->useSetboard) {
4886       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4887       sprintf(message, "setboard %s\n", fen);
4888       SendToProgram(message, cps);
4889       free(fen);
4890
4891     } else {
4892       ChessSquare *bp;
4893       int i, j;
4894       /* Kludge to set black to move, avoiding the troublesome and now
4895        * deprecated "black" command.
4896        */
4897       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4898
4899       SendToProgram("edit\n", cps);
4900       SendToProgram("#\n", cps);
4901       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4902         bp = &boards[moveNum][i][BOARD_LEFT];
4903         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4904           if ((int) *bp < (int) BlackPawn) {
4905             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4906                     AAA + j, ONE + i);
4907             if(message[0] == '+' || message[0] == '~') {
4908                 sprintf(message, "%c%c%c+\n",
4909                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4910                         AAA + j, ONE + i);
4911             }
4912             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4913                 message[1] = BOARD_RGHT   - 1 - j + '1';
4914                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4915             }
4916             SendToProgram(message, cps);
4917           }
4918         }
4919       }
4920     
4921       SendToProgram("c\n", cps);
4922       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4923         bp = &boards[moveNum][i][BOARD_LEFT];
4924         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4925           if (((int) *bp != (int) EmptySquare)
4926               && ((int) *bp >= (int) BlackPawn)) {
4927             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4928                     AAA + j, ONE + i);
4929             if(message[0] == '+' || message[0] == '~') {
4930                 sprintf(message, "%c%c%c+\n",
4931                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4932                         AAA + j, ONE + i);
4933             }
4934             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4935                 message[1] = BOARD_RGHT   - 1 - j + '1';
4936                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4937             }
4938             SendToProgram(message, cps);
4939           }
4940         }
4941       }
4942     
4943       SendToProgram(".\n", cps);
4944     }
4945     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4946 }
4947
4948 int
4949 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4950 {
4951     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4952     /* [HGM] add Shogi promotions */
4953     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4954     ChessSquare piece;
4955     ChessMove moveType;
4956     Boolean premove;
4957
4958     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4959     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4960
4961     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4962       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4963         return FALSE;
4964
4965     piece = boards[currentMove][fromY][fromX];
4966     if(gameInfo.variant == VariantShogi) {
4967         promotionZoneSize = 3;
4968         highestPromotingPiece = (int)WhiteFerz;
4969     }
4970
4971     // next weed out all moves that do not touch the promotion zone at all
4972     if((int)piece >= BlackPawn) {
4973         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4974              return FALSE;
4975         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4976     } else {
4977         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4978            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4979     }
4980
4981     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4982
4983     // weed out mandatory Shogi promotions
4984     if(gameInfo.variant == VariantShogi) {
4985         if(piece >= BlackPawn) {
4986             if(toY == 0 && piece == BlackPawn ||
4987                toY == 0 && piece == BlackQueen ||
4988                toY <= 1 && piece == BlackKnight) {
4989                 *promoChoice = '+';
4990                 return FALSE;
4991             }
4992         } else {
4993             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4994                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4995                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4996                 *promoChoice = '+';
4997                 return FALSE;
4998             }
4999         }
5000     }
5001
5002     // weed out obviously illegal Pawn moves
5003     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5004         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5005         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5006         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5007         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5008         // note we are not allowed to test for valid (non-)capture, due to premove
5009     }
5010
5011     // we either have a choice what to promote to, or (in Shogi) whether to promote
5012     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5013         *promoChoice = PieceToChar(BlackFerz);  // no choice
5014         return FALSE;
5015     }
5016     if(appData.alwaysPromoteToQueen) { // predetermined
5017         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5018              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5019         else *promoChoice = PieceToChar(BlackQueen);
5020         return FALSE;
5021     }
5022
5023     // suppress promotion popup on illegal moves that are not premoves
5024     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5025               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5026     if(appData.testLegality && !premove) {
5027         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5028                         epStatus[currentMove], castlingRights[currentMove],
5029                         fromY, fromX, toY, toX, NULLCHAR);
5030         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5031            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5032             return FALSE;
5033     }
5034
5035     return TRUE;
5036 }
5037
5038 int
5039 InPalace(row, column)
5040      int row, column;
5041 {   /* [HGM] for Xiangqi */
5042     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5043          column < (BOARD_WIDTH + 4)/2 &&
5044          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5045     return FALSE;
5046 }
5047
5048 int
5049 PieceForSquare (x, y)
5050      int x;
5051      int y;
5052 {
5053   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5054      return -1;
5055   else
5056      return boards[currentMove][y][x];
5057 }
5058
5059 int
5060 OKToStartUserMove(x, y)
5061      int x, y;
5062 {
5063     ChessSquare from_piece;
5064     int white_piece;
5065
5066     if (matchMode) return FALSE;
5067     if (gameMode == EditPosition) return TRUE;
5068
5069     if (x >= 0 && y >= 0)
5070       from_piece = boards[currentMove][y][x];
5071     else
5072       from_piece = EmptySquare;
5073
5074     if (from_piece == EmptySquare) return FALSE;
5075
5076     white_piece = (int)from_piece >= (int)WhitePawn &&
5077       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5078
5079     switch (gameMode) {
5080       case PlayFromGameFile:
5081       case AnalyzeFile:
5082       case TwoMachinesPlay:
5083       case EndOfGame:
5084         return FALSE;
5085
5086       case IcsObserving:
5087       case IcsIdle:
5088         return FALSE;
5089
5090       case MachinePlaysWhite:
5091       case IcsPlayingBlack:
5092         if (appData.zippyPlay) return FALSE;
5093         if (white_piece) {
5094             DisplayMoveError(_("You are playing Black"));
5095             return FALSE;
5096         }
5097         break;
5098
5099       case MachinePlaysBlack:
5100       case IcsPlayingWhite:
5101         if (appData.zippyPlay) return FALSE;
5102         if (!white_piece) {
5103             DisplayMoveError(_("You are playing White"));
5104             return FALSE;
5105         }
5106         break;
5107
5108       case EditGame:
5109         if (!white_piece && WhiteOnMove(currentMove)) {
5110             DisplayMoveError(_("It is White's turn"));
5111             return FALSE;
5112         }           
5113         if (white_piece && !WhiteOnMove(currentMove)) {
5114             DisplayMoveError(_("It is Black's turn"));
5115             return FALSE;
5116         }           
5117         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5118             /* Editing correspondence game history */
5119             /* Could disallow this or prompt for confirmation */
5120             cmailOldMove = -1;
5121         }
5122         if (currentMove < forwardMostMove) {
5123             /* Discarding moves */
5124             /* Could prompt for confirmation here,
5125                but I don't think that's such a good idea */
5126             forwardMostMove = currentMove;
5127         }
5128         break;
5129
5130       case BeginningOfGame:
5131         if (appData.icsActive) return FALSE;
5132         if (!appData.noChessProgram) {
5133             if (!white_piece) {
5134                 DisplayMoveError(_("You are playing White"));
5135                 return FALSE;
5136             }
5137         }
5138         break;
5139         
5140       case Training:
5141         if (!white_piece && WhiteOnMove(currentMove)) {
5142             DisplayMoveError(_("It is White's turn"));
5143             return FALSE;
5144         }           
5145         if (white_piece && !WhiteOnMove(currentMove)) {
5146             DisplayMoveError(_("It is Black's turn"));
5147             return FALSE;
5148         }           
5149         break;
5150
5151       default:
5152       case IcsExamining:
5153         break;
5154     }
5155     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5156         && gameMode != AnalyzeFile && gameMode != Training) {
5157         DisplayMoveError(_("Displayed position is not current"));
5158         return FALSE;
5159     }
5160     return TRUE;
5161 }
5162
5163 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5164 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5165 int lastLoadGameUseList = FALSE;
5166 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5167 ChessMove lastLoadGameStart = (ChessMove) 0;
5168
5169 ChessMove
5170 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5171      int fromX, fromY, toX, toY;
5172      int promoChar;
5173      Boolean captureOwn;
5174 {
5175     ChessMove moveType;
5176     ChessSquare pdown, pup;
5177
5178     /* Check if the user is playing in turn.  This is complicated because we
5179        let the user "pick up" a piece before it is his turn.  So the piece he
5180        tried to pick up may have been captured by the time he puts it down!
5181        Therefore we use the color the user is supposed to be playing in this
5182        test, not the color of the piece that is currently on the starting
5183        square---except in EditGame mode, where the user is playing both
5184        sides; fortunately there the capture race can't happen.  (It can
5185        now happen in IcsExamining mode, but that's just too bad.  The user
5186        will get a somewhat confusing message in that case.)
5187        */
5188
5189     switch (gameMode) {
5190       case PlayFromGameFile:
5191       case AnalyzeFile:
5192       case TwoMachinesPlay:
5193       case EndOfGame:
5194       case IcsObserving:
5195       case IcsIdle:
5196         /* We switched into a game mode where moves are not accepted,
5197            perhaps while the mouse button was down. */
5198         return ImpossibleMove;
5199
5200       case MachinePlaysWhite:
5201         /* User is moving for Black */
5202         if (WhiteOnMove(currentMove)) {
5203             DisplayMoveError(_("It is White's turn"));
5204             return ImpossibleMove;
5205         }
5206         break;
5207
5208       case MachinePlaysBlack:
5209         /* User is moving for White */
5210         if (!WhiteOnMove(currentMove)) {
5211             DisplayMoveError(_("It is Black's turn"));
5212             return ImpossibleMove;
5213         }
5214         break;
5215
5216       case EditGame:
5217       case IcsExamining:
5218       case BeginningOfGame:
5219       case AnalyzeMode:
5220       case Training:
5221         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5222             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5223             /* User is moving for Black */
5224             if (WhiteOnMove(currentMove)) {
5225                 DisplayMoveError(_("It is White's turn"));
5226                 return ImpossibleMove;
5227             }
5228         } else {
5229             /* User is moving for White */
5230             if (!WhiteOnMove(currentMove)) {
5231                 DisplayMoveError(_("It is Black's turn"));
5232                 return ImpossibleMove;
5233             }
5234         }
5235         break;
5236
5237       case IcsPlayingBlack:
5238         /* User is moving for Black */
5239         if (WhiteOnMove(currentMove)) {
5240             if (!appData.premove) {
5241                 DisplayMoveError(_("It is White's turn"));
5242             } else if (toX >= 0 && toY >= 0) {
5243                 premoveToX = toX;
5244                 premoveToY = toY;
5245                 premoveFromX = fromX;
5246                 premoveFromY = fromY;
5247                 premovePromoChar = promoChar;
5248                 gotPremove = 1;
5249                 if (appData.debugMode) 
5250                     fprintf(debugFP, "Got premove: fromX %d,"
5251                             "fromY %d, toX %d, toY %d\n",
5252                             fromX, fromY, toX, toY);
5253             }
5254             return ImpossibleMove;
5255         }
5256         break;
5257
5258       case IcsPlayingWhite:
5259         /* User is moving for White */
5260         if (!WhiteOnMove(currentMove)) {
5261             if (!appData.premove) {
5262                 DisplayMoveError(_("It is Black's turn"));
5263             } else if (toX >= 0 && toY >= 0) {
5264                 premoveToX = toX;
5265                 premoveToY = toY;
5266                 premoveFromX = fromX;
5267                 premoveFromY = fromY;
5268                 premovePromoChar = promoChar;
5269                 gotPremove = 1;
5270                 if (appData.debugMode) 
5271                     fprintf(debugFP, "Got premove: fromX %d,"
5272                             "fromY %d, toX %d, toY %d\n",
5273                             fromX, fromY, toX, toY);
5274             }
5275             return ImpossibleMove;
5276         }
5277         break;
5278
5279       default:
5280         break;
5281
5282       case EditPosition:
5283         /* EditPosition, empty square, or different color piece;
5284            click-click move is possible */
5285         if (toX == -2 || toY == -2) {
5286             boards[0][fromY][fromX] = EmptySquare;
5287             return AmbiguousMove;
5288         } else if (toX >= 0 && toY >= 0) {
5289             boards[0][toY][toX] = boards[0][fromY][fromX];
5290             boards[0][fromY][fromX] = EmptySquare;
5291             return AmbiguousMove;
5292         }
5293         return ImpossibleMove;
5294     }
5295
5296     if(toX < 0 || toY < 0) return ImpossibleMove;
5297     pdown = boards[currentMove][fromY][fromX];
5298     pup = boards[currentMove][toY][toX];
5299
5300     /* [HGM] If move started in holdings, it means a drop */
5301     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5302          if( pup != EmptySquare ) return ImpossibleMove;
5303          if(appData.testLegality) {
5304              /* it would be more logical if LegalityTest() also figured out
5305               * which drops are legal. For now we forbid pawns on back rank.
5306               * Shogi is on its own here...
5307               */
5308              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5309                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5310                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5311          }
5312          return WhiteDrop; /* Not needed to specify white or black yet */
5313     }
5314
5315     userOfferedDraw = FALSE;
5316         
5317     /* [HGM] always test for legality, to get promotion info */
5318     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5319                           epStatus[currentMove], castlingRights[currentMove],
5320                                          fromY, fromX, toY, toX, promoChar);
5321     /* [HGM] but possibly ignore an IllegalMove result */
5322     if (appData.testLegality) {
5323         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5324             DisplayMoveError(_("Illegal move"));
5325             return ImpossibleMove;
5326         }
5327     }
5328
5329     return moveType;
5330     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5331        function is made into one that returns an OK move type if FinishMove
5332        should be called. This to give the calling driver routine the
5333        opportunity to finish the userMove input with a promotion popup,
5334        without bothering the user with this for invalid or illegal moves */
5335
5336 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5337 }
5338
5339 /* Common tail of UserMoveEvent and DropMenuEvent */
5340 int
5341 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5342      ChessMove moveType;
5343      int fromX, fromY, toX, toY;
5344      /*char*/int promoChar;
5345 {
5346     char *bookHit = 0;
5347
5348     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5349         // [HGM] superchess: suppress promotions to non-available piece
5350         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5351         if(WhiteOnMove(currentMove)) {
5352             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5353         } else {
5354             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5355         }
5356     }
5357
5358     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5359        move type in caller when we know the move is a legal promotion */
5360     if(moveType == NormalMove && promoChar)
5361         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5362
5363     /* [HGM] convert drag-and-drop piece drops to standard form */
5364     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5365          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5366            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5367                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5368            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5369            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5370            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5371            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5372          fromY = DROP_RANK;
5373     }
5374
5375     /* [HGM] <popupFix> The following if has been moved here from
5376        UserMoveEvent(). Because it seemed to belong here (why not allow
5377        piece drops in training games?), and because it can only be
5378        performed after it is known to what we promote. */
5379     if (gameMode == Training) {
5380       /* compare the move played on the board to the next move in the
5381        * game. If they match, display the move and the opponent's response. 
5382        * If they don't match, display an error message.
5383        */
5384       int saveAnimate;
5385       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5386       CopyBoard(testBoard, boards[currentMove]);
5387       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5388
5389       if (CompareBoards(testBoard, boards[currentMove+1])) {
5390         ForwardInner(currentMove+1);
5391
5392         /* Autoplay the opponent's response.
5393          * if appData.animate was TRUE when Training mode was entered,
5394          * the response will be animated.
5395          */
5396         saveAnimate = appData.animate;
5397         appData.animate = animateTraining;
5398         ForwardInner(currentMove+1);
5399         appData.animate = saveAnimate;
5400
5401         /* check for the end of the game */
5402         if (currentMove >= forwardMostMove) {
5403           gameMode = PlayFromGameFile;
5404           ModeHighlight();
5405           SetTrainingModeOff();
5406           DisplayInformation(_("End of game"));
5407         }
5408       } else {
5409         DisplayError(_("Incorrect move"), 0);
5410       }
5411       return 1;
5412     }
5413
5414   /* Ok, now we know that the move is good, so we can kill
5415      the previous line in Analysis Mode */
5416   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5417     forwardMostMove = currentMove;
5418   }
5419
5420   /* If we need the chess program but it's dead, restart it */
5421   ResurrectChessProgram();
5422
5423   /* A user move restarts a paused game*/
5424   if (pausing)
5425     PauseEvent();
5426
5427   thinkOutput[0] = NULLCHAR;
5428
5429   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5430
5431   if (gameMode == BeginningOfGame) {
5432     if (appData.noChessProgram) {
5433       gameMode = EditGame;
5434       SetGameInfo();
5435     } else {
5436       char buf[MSG_SIZ];
5437       gameMode = MachinePlaysBlack;
5438       StartClocks();
5439       SetGameInfo();
5440       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5441       DisplayTitle(buf);
5442       if (first.sendName) {
5443         sprintf(buf, "name %s\n", gameInfo.white);
5444         SendToProgram(buf, &first);
5445       }
5446       StartClocks();
5447     }
5448     ModeHighlight();
5449   }
5450
5451   /* Relay move to ICS or chess engine */
5452   if (appData.icsActive) {
5453     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5454         gameMode == IcsExamining) {
5455       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5456       ics_user_moved = 1;
5457     }
5458   } else {
5459     if (first.sendTime && (gameMode == BeginningOfGame ||
5460                            gameMode == MachinePlaysWhite ||
5461                            gameMode == MachinePlaysBlack)) {
5462       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5463     }
5464     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5465          // [HGM] book: if program might be playing, let it use book
5466         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5467         first.maybeThinking = TRUE;
5468     } else SendMoveToProgram(forwardMostMove-1, &first);
5469     if (currentMove == cmailOldMove + 1) {
5470       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5471     }
5472   }
5473
5474   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5475
5476   switch (gameMode) {
5477   case EditGame:
5478     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5479                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5480     case MT_NONE:
5481     case MT_CHECK:
5482       break;
5483     case MT_CHECKMATE:
5484     case MT_STAINMATE:
5485       if (WhiteOnMove(currentMove)) {
5486         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5487       } else {
5488         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5489       }
5490       break;
5491     case MT_STALEMATE:
5492       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5493       break;
5494     }
5495     break;
5496     
5497   case MachinePlaysBlack:
5498   case MachinePlaysWhite:
5499     /* disable certain menu options while machine is thinking */
5500     SetMachineThinkingEnables();
5501     break;
5502
5503   default:
5504     break;
5505   }
5506
5507   if(bookHit) { // [HGM] book: simulate book reply
5508         static char bookMove[MSG_SIZ]; // a bit generous?
5509
5510         programStats.nodes = programStats.depth = programStats.time = 
5511         programStats.score = programStats.got_only_move = 0;
5512         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5513
5514         strcpy(bookMove, "move ");
5515         strcat(bookMove, bookHit);
5516         HandleMachineMove(bookMove, &first);
5517   }
5518   return 1;
5519 }
5520
5521 void
5522 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5523      int fromX, fromY, toX, toY;
5524      int promoChar;
5525 {
5526     /* [HGM] This routine was added to allow calling of its two logical
5527        parts from other modules in the old way. Before, UserMoveEvent()
5528        automatically called FinishMove() if the move was OK, and returned
5529        otherwise. I separated the two, in order to make it possible to
5530        slip a promotion popup in between. But that it always needs two
5531        calls, to the first part, (now called UserMoveTest() ), and to
5532        FinishMove if the first part succeeded. Calls that do not need
5533        to do anything in between, can call this routine the old way. 
5534     */
5535     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5536 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5537     if(moveType == AmbiguousMove)
5538         DrawPosition(FALSE, boards[currentMove]);
5539     else if(moveType != ImpossibleMove && moveType != Comment)
5540         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5541 }
5542
5543 void LeftClick(ClickType clickType, int xPix, int yPix)
5544 {
5545     int x, y;
5546     Boolean saveAnimate;
5547     static int second = 0, promotionChoice = 0;
5548     char promoChoice = NULLCHAR;
5549
5550     if (clickType == Press) ErrorPopDown();
5551
5552     x = EventToSquare(xPix, BOARD_WIDTH);
5553     y = EventToSquare(yPix, BOARD_HEIGHT);
5554     if (!flipView && y >= 0) {
5555         y = BOARD_HEIGHT - 1 - y;
5556     }
5557     if (flipView && x >= 0) {
5558         x = BOARD_WIDTH - 1 - x;
5559     }
5560
5561     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5562         if(clickType == Release) return; // ignore upclick of click-click destination
5563         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5564         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5565         if(gameInfo.holdingsWidth && 
5566                 (WhiteOnMove(currentMove) 
5567                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5568                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5569             // click in right holdings, for determining promotion piece
5570             ChessSquare p = boards[currentMove][y][x];
5571             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5572             if(p != EmptySquare) {
5573                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5574                 fromX = fromY = -1;
5575                 return;
5576             }
5577         }
5578         DrawPosition(FALSE, boards[currentMove]);
5579         return;
5580     }
5581
5582     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5583     if(clickType == Press
5584             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5585               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5586               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5587         return;
5588
5589     if (fromX == -1) {
5590         if (clickType == Press) {
5591             /* First square */
5592             if (OKToStartUserMove(x, y)) {
5593                 fromX = x;
5594                 fromY = y;
5595                 second = 0;
5596                 DragPieceBegin(xPix, yPix);
5597                 if (appData.highlightDragging) {
5598                     SetHighlights(x, y, -1, -1);
5599                 }
5600             }
5601         }
5602         return;
5603     }
5604
5605     /* fromX != -1 */
5606     if (clickType == Press && gameMode != EditPosition) {
5607         ChessSquare fromP;
5608         ChessSquare toP;
5609         int frc;
5610
5611         // ignore off-board to clicks
5612         if(y < 0 || x < 0) return;
5613
5614         /* Check if clicking again on the same color piece */
5615         fromP = boards[currentMove][fromY][fromX];
5616         toP = boards[currentMove][y][x];
5617         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5618         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5619              WhitePawn <= toP && toP <= WhiteKing &&
5620              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5621              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5622             (BlackPawn <= fromP && fromP <= BlackKing && 
5623              BlackPawn <= toP && toP <= BlackKing &&
5624              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5625              !(fromP == BlackKing && toP == BlackRook && frc))) {
5626             /* Clicked again on same color piece -- changed his mind */
5627             second = (x == fromX && y == fromY);
5628             if (appData.highlightDragging) {
5629                 SetHighlights(x, y, -1, -1);
5630             } else {
5631                 ClearHighlights();
5632             }
5633             if (OKToStartUserMove(x, y)) {
5634                 fromX = x;
5635                 fromY = y;
5636                 DragPieceBegin(xPix, yPix);
5637             }
5638             return;
5639         }
5640         // ignore clicks on holdings
5641         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5642     }
5643
5644     if (clickType == Release && x == fromX && y == fromY) {
5645         DragPieceEnd(xPix, yPix);
5646         if (appData.animateDragging) {
5647             /* Undo animation damage if any */
5648             DrawPosition(FALSE, NULL);
5649         }
5650         if (second) {
5651             /* Second up/down in same square; just abort move */
5652             second = 0;
5653             fromX = fromY = -1;
5654             ClearHighlights();
5655             gotPremove = 0;
5656             ClearPremoveHighlights();
5657         } else {
5658             /* First upclick in same square; start click-click mode */
5659             SetHighlights(x, y, -1, -1);
5660         }
5661         return;
5662     }
5663
5664     /* we now have a different from- and (possibly off-board) to-square */
5665     /* Completed move */
5666     toX = x;
5667     toY = y;
5668     saveAnimate = appData.animate;
5669     if (clickType == Press) {
5670         /* Finish clickclick move */
5671         if (appData.animate || appData.highlightLastMove) {
5672             SetHighlights(fromX, fromY, toX, toY);
5673         } else {
5674             ClearHighlights();
5675         }
5676     } else {
5677         /* Finish drag move */
5678         if (appData.highlightLastMove) {
5679             SetHighlights(fromX, fromY, toX, toY);
5680         } else {
5681             ClearHighlights();
5682         }
5683         DragPieceEnd(xPix, yPix);
5684         /* Don't animate move and drag both */
5685         appData.animate = FALSE;
5686     }
5687
5688     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5689     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5690         ClearHighlights();
5691         fromX = fromY = -1;
5692         DrawPosition(TRUE, NULL);
5693         return;
5694     }
5695
5696     // off-board moves should not be highlighted
5697     if(x < 0 || x < 0) ClearHighlights();
5698
5699     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5700         SetHighlights(fromX, fromY, toX, toY);
5701         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5702             // [HGM] super: promotion to captured piece selected from holdings
5703             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5704             promotionChoice = TRUE;
5705             // kludge follows to temporarily execute move on display, without promoting yet
5706             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5707             boards[currentMove][toY][toX] = p;
5708             DrawPosition(FALSE, boards[currentMove]);
5709             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5710             boards[currentMove][toY][toX] = q;
5711             DisplayMessage("Click in holdings to choose piece", "");
5712             return;
5713         }
5714         PromotionPopUp();
5715     } else {
5716         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5717         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5718         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5719         fromX = fromY = -1;
5720     }
5721     appData.animate = saveAnimate;
5722     if (appData.animate || appData.animateDragging) {
5723         /* Undo animation damage if needed */
5724         DrawPosition(FALSE, NULL);
5725     }
5726 }
5727
5728 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5729 {
5730 //    char * hint = lastHint;
5731     FrontEndProgramStats stats;
5732
5733     stats.which = cps == &first ? 0 : 1;
5734     stats.depth = cpstats->depth;
5735     stats.nodes = cpstats->nodes;
5736     stats.score = cpstats->score;
5737     stats.time = cpstats->time;
5738     stats.pv = cpstats->movelist;
5739     stats.hint = lastHint;
5740     stats.an_move_index = 0;
5741     stats.an_move_count = 0;
5742
5743     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5744         stats.hint = cpstats->move_name;
5745         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5746         stats.an_move_count = cpstats->nr_moves;
5747     }
5748
5749     SetProgramStats( &stats );
5750 }
5751
5752 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5753 {   // [HGM] book: this routine intercepts moves to simulate book replies
5754     char *bookHit = NULL;
5755
5756     //first determine if the incoming move brings opponent into his book
5757     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5758         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5759     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5760     if(bookHit != NULL && !cps->bookSuspend) {
5761         // make sure opponent is not going to reply after receiving move to book position
5762         SendToProgram("force\n", cps);
5763         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5764     }
5765     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5766     // now arrange restart after book miss
5767     if(bookHit) {
5768         // after a book hit we never send 'go', and the code after the call to this routine
5769         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5770         char buf[MSG_SIZ];
5771         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5772         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5773         SendToProgram(buf, cps);
5774         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5775     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5776         SendToProgram("go\n", cps);
5777         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5778     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5779         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5780             SendToProgram("go\n", cps); 
5781         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5782     }
5783     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5784 }
5785
5786 char *savedMessage;
5787 ChessProgramState *savedState;
5788 void DeferredBookMove(void)
5789 {
5790         if(savedState->lastPing != savedState->lastPong)
5791                     ScheduleDelayedEvent(DeferredBookMove, 10);
5792         else
5793         HandleMachineMove(savedMessage, savedState);
5794 }
5795
5796 void
5797 HandleMachineMove(message, cps)
5798      char *message;
5799      ChessProgramState *cps;
5800 {
5801     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5802     char realname[MSG_SIZ];
5803     int fromX, fromY, toX, toY;
5804     ChessMove moveType;
5805     char promoChar;
5806     char *p;
5807     int machineWhite;
5808     char *bookHit;
5809
5810 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5811     /*
5812      * Kludge to ignore BEL characters
5813      */
5814     while (*message == '\007') message++;
5815
5816     /*
5817      * [HGM] engine debug message: ignore lines starting with '#' character
5818      */
5819     if(cps->debug && *message == '#') return;
5820
5821     /*
5822      * Look for book output
5823      */
5824     if (cps == &first && bookRequested) {
5825         if (message[0] == '\t' || message[0] == ' ') {
5826             /* Part of the book output is here; append it */
5827             strcat(bookOutput, message);
5828             strcat(bookOutput, "  \n");
5829             return;
5830         } else if (bookOutput[0] != NULLCHAR) {
5831             /* All of book output has arrived; display it */
5832             char *p = bookOutput;
5833             while (*p != NULLCHAR) {
5834                 if (*p == '\t') *p = ' ';
5835                 p++;
5836             }
5837             DisplayInformation(bookOutput);
5838             bookRequested = FALSE;
5839             /* Fall through to parse the current output */
5840         }
5841     }
5842
5843     /*
5844      * Look for machine move.
5845      */
5846     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5847         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5848     {
5849         /* This method is only useful on engines that support ping */
5850         if (cps->lastPing != cps->lastPong) {
5851           if (gameMode == BeginningOfGame) {
5852             /* Extra move from before last new; ignore */
5853             if (appData.debugMode) {
5854                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5855             }
5856           } else {
5857             if (appData.debugMode) {
5858                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5859                         cps->which, gameMode);
5860             }
5861
5862             SendToProgram("undo\n", cps);
5863           }
5864           return;
5865         }
5866
5867         switch (gameMode) {
5868           case BeginningOfGame:
5869             /* Extra move from before last reset; ignore */
5870             if (appData.debugMode) {
5871                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5872             }
5873             return;
5874
5875           case EndOfGame:
5876           case IcsIdle:
5877           default:
5878             /* Extra move after we tried to stop.  The mode test is
5879                not a reliable way of detecting this problem, but it's
5880                the best we can do on engines that don't support ping.
5881             */
5882             if (appData.debugMode) {
5883                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5884                         cps->which, gameMode);
5885             }
5886             SendToProgram("undo\n", cps);
5887             return;
5888
5889           case MachinePlaysWhite:
5890           case IcsPlayingWhite:
5891             machineWhite = TRUE;
5892             break;
5893
5894           case MachinePlaysBlack:
5895           case IcsPlayingBlack:
5896             machineWhite = FALSE;
5897             break;
5898
5899           case TwoMachinesPlay:
5900             machineWhite = (cps->twoMachinesColor[0] == 'w');
5901             break;
5902         }
5903         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5904             if (appData.debugMode) {
5905                 fprintf(debugFP,
5906                         "Ignoring move out of turn by %s, gameMode %d"
5907                         ", forwardMost %d\n",
5908                         cps->which, gameMode, forwardMostMove);
5909             }
5910             return;
5911         }
5912
5913     if (appData.debugMode) { int f = forwardMostMove;
5914         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5915                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5916     }
5917         if(cps->alphaRank) AlphaRank(machineMove, 4);
5918         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5919                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5920             /* Machine move could not be parsed; ignore it. */
5921             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5922                     machineMove, cps->which);
5923             DisplayError(buf1, 0);
5924             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5925                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5926             if (gameMode == TwoMachinesPlay) {
5927               GameEnds(machineWhite ? BlackWins : WhiteWins,
5928                        buf1, GE_XBOARD);
5929             }
5930             return;
5931         }
5932
5933         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5934         /* So we have to redo legality test with true e.p. status here,  */
5935         /* to make sure an illegal e.p. capture does not slip through,   */
5936         /* to cause a forfeit on a justified illegal-move complaint      */
5937         /* of the opponent.                                              */
5938         if( gameMode==TwoMachinesPlay && appData.testLegality
5939             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5940                                                               ) {
5941            ChessMove moveType;
5942            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5943                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5944                              fromY, fromX, toY, toX, promoChar);
5945             if (appData.debugMode) {
5946                 int i;
5947                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5948                     castlingRights[forwardMostMove][i], castlingRank[i]);
5949                 fprintf(debugFP, "castling rights\n");
5950             }
5951             if(moveType == IllegalMove) {
5952                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5953                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5954                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5955                            buf1, GE_XBOARD);
5956                 return;
5957            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5958            /* [HGM] Kludge to handle engines that send FRC-style castling
5959               when they shouldn't (like TSCP-Gothic) */
5960            switch(moveType) {
5961              case WhiteASideCastleFR:
5962              case BlackASideCastleFR:
5963                toX+=2;
5964                currentMoveString[2]++;
5965                break;
5966              case WhiteHSideCastleFR:
5967              case BlackHSideCastleFR:
5968                toX--;
5969                currentMoveString[2]--;
5970                break;
5971              default: ; // nothing to do, but suppresses warning of pedantic compilers
5972            }
5973         }
5974         hintRequested = FALSE;
5975         lastHint[0] = NULLCHAR;
5976         bookRequested = FALSE;
5977         /* Program may be pondering now */
5978         cps->maybeThinking = TRUE;
5979         if (cps->sendTime == 2) cps->sendTime = 1;
5980         if (cps->offeredDraw) cps->offeredDraw--;
5981
5982 #if ZIPPY
5983         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5984             first.initDone) {
5985           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5986           ics_user_moved = 1;
5987           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5988                 char buf[3*MSG_SIZ];
5989
5990                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5991                         programStats.score / 100.,
5992                         programStats.depth,
5993                         programStats.time / 100.,
5994                         (unsigned int)programStats.nodes,
5995                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5996                         programStats.movelist);
5997                 SendToICS(buf);
5998 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5999           }
6000         }
6001 #endif
6002         /* currentMoveString is set as a side-effect of ParseOneMove */
6003         strcpy(machineMove, currentMoveString);
6004         strcat(machineMove, "\n");
6005         strcpy(moveList[forwardMostMove], machineMove);
6006
6007         /* [AS] Save move info and clear stats for next move */
6008         pvInfoList[ forwardMostMove ].score = programStats.score;
6009         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6010         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6011         ClearProgramStats();
6012         thinkOutput[0] = NULLCHAR;
6013         hiddenThinkOutputState = 0;
6014
6015         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6016
6017         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6018         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6019             int count = 0;
6020
6021             while( count < adjudicateLossPlies ) {
6022                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6023
6024                 if( count & 1 ) {
6025                     score = -score; /* Flip score for winning side */
6026                 }
6027
6028                 if( score > adjudicateLossThreshold ) {
6029                     break;
6030                 }
6031
6032                 count++;
6033             }
6034
6035             if( count >= adjudicateLossPlies ) {
6036                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6037
6038                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6039                     "Xboard adjudication", 
6040                     GE_XBOARD );
6041
6042                 return;
6043             }
6044         }
6045
6046         if( gameMode == TwoMachinesPlay ) {
6047           // [HGM] some adjudications useful with buggy engines
6048             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6049           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6050
6051
6052             if( appData.testLegality )
6053             {   /* [HGM] Some more adjudications for obstinate engines */
6054                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6055                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6056                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6057                 static int moveCount = 6;
6058                 ChessMove result;
6059                 char *reason = NULL;
6060
6061                 /* Count what is on board. */
6062                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6063                 {   ChessSquare p = boards[forwardMostMove][i][j];
6064                     int m=i;
6065
6066                     switch((int) p)
6067                     {   /* count B,N,R and other of each side */
6068                         case WhiteKing:
6069                         case BlackKing:
6070                              NrK++; break; // [HGM] atomic: count Kings
6071                         case WhiteKnight:
6072                              NrWN++; break;
6073                         case WhiteBishop:
6074                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6075                              bishopsColor |= 1 << ((i^j)&1);
6076                              NrWB++; break;
6077                         case BlackKnight:
6078                              NrBN++; break;
6079                         case BlackBishop:
6080                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6081                              bishopsColor |= 1 << ((i^j)&1);
6082                              NrBB++; break;
6083                         case WhiteRook:
6084                              NrWR++; break;
6085                         case BlackRook:
6086                              NrBR++; break;
6087                         case WhiteQueen:
6088                              NrWQ++; break;
6089                         case BlackQueen:
6090                              NrBQ++; break;
6091                         case EmptySquare: 
6092                              break;
6093                         case BlackPawn:
6094                              m = 7-i;
6095                         case WhitePawn:
6096                              PawnAdvance += m; NrPawns++;
6097                     }
6098                     NrPieces += (p != EmptySquare);
6099                     NrW += ((int)p < (int)BlackPawn);
6100                     if(gameInfo.variant == VariantXiangqi && 
6101                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6102                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6103                         NrW -= ((int)p < (int)BlackPawn);
6104                     }
6105                 }
6106
6107                 /* Some material-based adjudications that have to be made before stalemate test */
6108                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6109                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6110                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6111                      if(appData.checkMates) {
6112                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6113                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6114                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6115                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6116                          return;
6117                      }
6118                 }
6119
6120                 /* Bare King in Shatranj (loses) or Losers (wins) */
6121                 if( NrW == 1 || NrPieces - NrW == 1) {
6122                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6123                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6124                      if(appData.checkMates) {
6125                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6126                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6127                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6128                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6129                          return;
6130                      }
6131                   } else
6132                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6133                   {    /* bare King */
6134                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6135                         if(appData.checkMates) {
6136                             /* but only adjudicate if adjudication enabled */
6137                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6138                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6139                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6140                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6141                             return;
6142                         }
6143                   }
6144                 } else bare = 1;
6145
6146
6147             // don't wait for engine to announce game end if we can judge ourselves
6148             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6149                                        castlingRights[forwardMostMove]) ) {
6150               case MT_CHECK:
6151                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6152                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6153                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6154                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6155                             checkCnt++;
6156                         if(checkCnt >= 2) {
6157                             reason = "Xboard adjudication: 3rd check";
6158                             epStatus[forwardMostMove] = EP_CHECKMATE;
6159                             break;
6160                         }
6161                     }
6162                 }
6163               case MT_NONE:
6164               default:
6165                 break;
6166               case MT_STALEMATE:
6167               case MT_STAINMATE:
6168                 reason = "Xboard adjudication: Stalemate";
6169                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6170                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6171                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6172                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6173                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6174                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6175                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6176                                                                         EP_CHECKMATE : EP_WINS);
6177                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6178                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6179                 }
6180                 break;
6181               case MT_CHECKMATE:
6182                 reason = "Xboard adjudication: Checkmate";
6183                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6184                 break;
6185             }
6186
6187                 switch(i = epStatus[forwardMostMove]) {
6188                     case EP_STALEMATE:
6189                         result = GameIsDrawn; break;
6190                     case EP_CHECKMATE:
6191                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6192                     case EP_WINS:
6193                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6194                     default:
6195                         result = (ChessMove) 0;
6196                 }
6197                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6198                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6199                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6200                     GameEnds( result, reason, GE_XBOARD );
6201                     return;
6202                 }
6203
6204                 /* Next absolutely insufficient mating material. */
6205                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6206                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6207                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6208                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6209                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6210
6211                      /* always flag draws, for judging claims */
6212                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6213
6214                      if(appData.materialDraws) {
6215                          /* but only adjudicate them if adjudication enabled */
6216                          SendToProgram("force\n", cps->other); // suppress reply
6217                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6218                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6219                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6220                          return;
6221                      }
6222                 }
6223
6224                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6225                 if(NrPieces == 4 && 
6226                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6227                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6228                    || NrWN==2 || NrBN==2     /* KNNK */
6229                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6230                   ) ) {
6231                      if(--moveCount < 0 && appData.trivialDraws)
6232                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6233                           SendToProgram("force\n", cps->other); // suppress reply
6234                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6235                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6236                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6237                           return;
6238                      }
6239                 } else moveCount = 6;
6240             }
6241           }
6242
6243                 /* Check for rep-draws */
6244                 count = 0;
6245                 for(k = forwardMostMove-2;
6246                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6247                         epStatus[k] < EP_UNKNOWN &&
6248                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6249                     k-=2)
6250                 {   int rights=0;
6251                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6252                         /* compare castling rights */
6253                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6254                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6255                                 rights++; /* King lost rights, while rook still had them */
6256                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6257                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6258                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6259                                    rights++; /* but at least one rook lost them */
6260                         }
6261                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6262                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6263                                 rights++; 
6264                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6265                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6266                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6267                                    rights++;
6268                         }
6269                         if( rights == 0 && ++count > appData.drawRepeats-2
6270                             && appData.drawRepeats > 1) {
6271                              /* adjudicate after user-specified nr of repeats */
6272                              SendToProgram("force\n", cps->other); // suppress reply
6273                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6274                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6276                                 // [HGM] xiangqi: check for forbidden perpetuals
6277                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6278                                 for(m=forwardMostMove; m>k; m-=2) {
6279                                     if(MateTest(boards[m], PosFlags(m), 
6280                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6281                                         ourPerpetual = 0; // the current mover did not always check
6282                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6283                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6284                                         hisPerpetual = 0; // the opponent did not always check
6285                                 }
6286                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6287                                                                         ourPerpetual, hisPerpetual);
6288                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6289                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6290                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6291                                     return;
6292                                 }
6293                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6294                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6295                                 // Now check for perpetual chases
6296                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6297                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6298                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6299                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6300                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6301                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6302                                         return;
6303                                     }
6304                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6305                                         break; // Abort repetition-checking loop.
6306                                 }
6307                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6308                              }
6309                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6310                              return;
6311                         }
6312                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6313                              epStatus[forwardMostMove] = EP_REP_DRAW;
6314                     }
6315                 }
6316
6317                 /* Now we test for 50-move draws. Determine ply count */
6318                 count = forwardMostMove;
6319                 /* look for last irreversble move */
6320                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6321                     count--;
6322                 /* if we hit starting position, add initial plies */
6323                 if( count == backwardMostMove )
6324                     count -= initialRulePlies;
6325                 count = forwardMostMove - count; 
6326                 if( count >= 100)
6327                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6328                          /* this is used to judge if draw claims are legal */
6329                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6330                          SendToProgram("force\n", cps->other); // suppress reply
6331                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6332                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6333                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6334                          return;
6335                 }
6336
6337                 /* if draw offer is pending, treat it as a draw claim
6338                  * when draw condition present, to allow engines a way to
6339                  * claim draws before making their move to avoid a race
6340                  * condition occurring after their move
6341                  */
6342                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6343                          char *p = NULL;
6344                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6345                              p = "Draw claim: 50-move rule";
6346                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6347                              p = "Draw claim: 3-fold repetition";
6348                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6349                              p = "Draw claim: insufficient mating material";
6350                          if( p != NULL ) {
6351                              SendToProgram("force\n", cps->other); // suppress reply
6352                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6353                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6354                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6355                              return;
6356                          }
6357                 }
6358
6359
6360                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6361                     SendToProgram("force\n", cps->other); // suppress reply
6362                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6363                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364
6365                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6366
6367                     return;
6368                 }
6369         }
6370
6371         bookHit = NULL;
6372         if (gameMode == TwoMachinesPlay) {
6373             /* [HGM] relaying draw offers moved to after reception of move */
6374             /* and interpreting offer as claim if it brings draw condition */
6375             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6376                 SendToProgram("draw\n", cps->other);
6377             }
6378             if (cps->other->sendTime) {
6379                 SendTimeRemaining(cps->other,
6380                                   cps->other->twoMachinesColor[0] == 'w');
6381             }
6382             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6383             if (firstMove && !bookHit) {
6384                 firstMove = FALSE;
6385                 if (cps->other->useColors) {
6386                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6387                 }
6388                 SendToProgram("go\n", cps->other);
6389             }
6390             cps->other->maybeThinking = TRUE;
6391         }
6392
6393         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6394         
6395         if (!pausing && appData.ringBellAfterMoves) {
6396             RingBell();
6397         }
6398
6399         /* 
6400          * Reenable menu items that were disabled while
6401          * machine was thinking
6402          */
6403         if (gameMode != TwoMachinesPlay)
6404             SetUserThinkingEnables();
6405
6406         // [HGM] book: after book hit opponent has received move and is now in force mode
6407         // force the book reply into it, and then fake that it outputted this move by jumping
6408         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6409         if(bookHit) {
6410                 static char bookMove[MSG_SIZ]; // a bit generous?
6411
6412                 strcpy(bookMove, "move ");
6413                 strcat(bookMove, bookHit);
6414                 message = bookMove;
6415                 cps = cps->other;
6416                 programStats.nodes = programStats.depth = programStats.time = 
6417                 programStats.score = programStats.got_only_move = 0;
6418                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6419
6420                 if(cps->lastPing != cps->lastPong) {
6421                     savedMessage = message; // args for deferred call
6422                     savedState = cps;
6423                     ScheduleDelayedEvent(DeferredBookMove, 10);
6424                     return;
6425                 }
6426                 goto FakeBookMove;
6427         }
6428
6429         return;
6430     }
6431
6432     /* Set special modes for chess engines.  Later something general
6433      *  could be added here; for now there is just one kludge feature,
6434      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6435      *  when "xboard" is given as an interactive command.
6436      */
6437     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6438         cps->useSigint = FALSE;
6439         cps->useSigterm = FALSE;
6440     }
6441     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6442       ParseFeatures(message+8, cps);
6443       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6444     }
6445
6446     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6447      * want this, I was asked to put it in, and obliged.
6448      */
6449     if (!strncmp(message, "setboard ", 9)) {
6450         Board initial_position; int i;
6451
6452         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6453
6454         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6455             DisplayError(_("Bad FEN received from engine"), 0);
6456             return ;
6457         } else {
6458            Reset(TRUE, FALSE);
6459            CopyBoard(boards[0], initial_position);
6460            initialRulePlies = FENrulePlies;
6461            epStatus[0] = FENepStatus;
6462            for( i=0; i<nrCastlingRights; i++ )
6463                 castlingRights[0][i] = FENcastlingRights[i];
6464            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6465            else gameMode = MachinePlaysBlack;                 
6466            DrawPosition(FALSE, boards[currentMove]);
6467         }
6468         return;
6469     }
6470
6471     /*
6472      * Look for communication commands
6473      */
6474     if (!strncmp(message, "telluser ", 9)) {
6475         DisplayNote(message + 9);
6476         return;
6477     }
6478     if (!strncmp(message, "tellusererror ", 14)) {
6479         DisplayError(message + 14, 0);
6480         return;
6481     }
6482     if (!strncmp(message, "tellopponent ", 13)) {
6483       if (appData.icsActive) {
6484         if (loggedOn) {
6485           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6486           SendToICS(buf1);
6487         }
6488       } else {
6489         DisplayNote(message + 13);
6490       }
6491       return;
6492     }
6493     if (!strncmp(message, "tellothers ", 11)) {
6494       if (appData.icsActive) {
6495         if (loggedOn) {
6496           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6497           SendToICS(buf1);
6498         }
6499       }
6500       return;
6501     }
6502     if (!strncmp(message, "tellall ", 8)) {
6503       if (appData.icsActive) {
6504         if (loggedOn) {
6505           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6506           SendToICS(buf1);
6507         }
6508       } else {
6509         DisplayNote(message + 8);
6510       }
6511       return;
6512     }
6513     if (strncmp(message, "warning", 7) == 0) {
6514         /* Undocumented feature, use tellusererror in new code */
6515         DisplayError(message, 0);
6516         return;
6517     }
6518     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6519         strcpy(realname, cps->tidy);
6520         strcat(realname, " query");
6521         AskQuestion(realname, buf2, buf1, cps->pr);
6522         return;
6523     }
6524     /* Commands from the engine directly to ICS.  We don't allow these to be 
6525      *  sent until we are logged on. Crafty kibitzes have been known to 
6526      *  interfere with the login process.
6527      */
6528     if (loggedOn) {
6529         if (!strncmp(message, "tellics ", 8)) {
6530             SendToICS(message + 8);
6531             SendToICS("\n");
6532             return;
6533         }
6534         if (!strncmp(message, "tellicsnoalias ", 15)) {
6535             SendToICS(ics_prefix);
6536             SendToICS(message + 15);
6537             SendToICS("\n");
6538             return;
6539         }
6540         /* The following are for backward compatibility only */
6541         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6542             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6543             SendToICS(ics_prefix);
6544             SendToICS(message);
6545             SendToICS("\n");
6546             return;
6547         }
6548     }
6549     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6550         return;
6551     }
6552     /*
6553      * If the move is illegal, cancel it and redraw the board.
6554      * Also deal with other error cases.  Matching is rather loose
6555      * here to accommodate engines written before the spec.
6556      */
6557     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6558         strncmp(message, "Error", 5) == 0) {
6559         if (StrStr(message, "name") || 
6560             StrStr(message, "rating") || StrStr(message, "?") ||
6561             StrStr(message, "result") || StrStr(message, "board") ||
6562             StrStr(message, "bk") || StrStr(message, "computer") ||
6563             StrStr(message, "variant") || StrStr(message, "hint") ||
6564             StrStr(message, "random") || StrStr(message, "depth") ||
6565             StrStr(message, "accepted")) {
6566             return;
6567         }
6568         if (StrStr(message, "protover")) {
6569           /* Program is responding to input, so it's apparently done
6570              initializing, and this error message indicates it is
6571              protocol version 1.  So we don't need to wait any longer
6572              for it to initialize and send feature commands. */
6573           FeatureDone(cps, 1);
6574           cps->protocolVersion = 1;
6575           return;
6576         }
6577         cps->maybeThinking = FALSE;
6578
6579         if (StrStr(message, "draw")) {
6580             /* Program doesn't have "draw" command */
6581             cps->sendDrawOffers = 0;
6582             return;
6583         }
6584         if (cps->sendTime != 1 &&
6585             (StrStr(message, "time") || StrStr(message, "otim"))) {
6586           /* Program apparently doesn't have "time" or "otim" command */
6587           cps->sendTime = 0;
6588           return;
6589         }
6590         if (StrStr(message, "analyze")) {
6591             cps->analysisSupport = FALSE;
6592             cps->analyzing = FALSE;
6593             Reset(FALSE, TRUE);
6594             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6595             DisplayError(buf2, 0);
6596             return;
6597         }
6598         if (StrStr(message, "(no matching move)st")) {
6599           /* Special kludge for GNU Chess 4 only */
6600           cps->stKludge = TRUE;
6601           SendTimeControl(cps, movesPerSession, timeControl,
6602                           timeIncrement, appData.searchDepth,
6603                           searchTime);
6604           return;
6605         }
6606         if (StrStr(message, "(no matching move)sd")) {
6607           /* Special kludge for GNU Chess 4 only */
6608           cps->sdKludge = TRUE;
6609           SendTimeControl(cps, movesPerSession, timeControl,
6610                           timeIncrement, appData.searchDepth,
6611                           searchTime);
6612           return;
6613         }
6614         if (!StrStr(message, "llegal")) {
6615             return;
6616         }
6617         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6618             gameMode == IcsIdle) return;
6619         if (forwardMostMove <= backwardMostMove) return;
6620         if (pausing) PauseEvent();
6621       if(appData.forceIllegal) {
6622             // [HGM] illegal: machine refused move; force position after move into it
6623           SendToProgram("force\n", cps);
6624           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6625                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6626                 // when black is to move, while there might be nothing on a2 or black
6627                 // might already have the move. So send the board as if white has the move.
6628                 // But first we must change the stm of the engine, as it refused the last move
6629                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6630                 if(WhiteOnMove(forwardMostMove)) {
6631                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6632                     SendBoard(cps, forwardMostMove); // kludgeless board
6633                 } else {
6634                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6635                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6636                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6637                 }
6638           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6639             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6640                  gameMode == TwoMachinesPlay)
6641               SendToProgram("go\n", cps);
6642             return;
6643       } else
6644         if (gameMode == PlayFromGameFile) {
6645             /* Stop reading this game file */
6646             gameMode = EditGame;
6647             ModeHighlight();
6648         }
6649         currentMove = --forwardMostMove;
6650         DisplayMove(currentMove-1); /* before DisplayMoveError */
6651         SwitchClocks();
6652         DisplayBothClocks();
6653         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6654                 parseList[currentMove], cps->which);
6655         DisplayMoveError(buf1);
6656         DrawPosition(FALSE, boards[currentMove]);
6657
6658         /* [HGM] illegal-move claim should forfeit game when Xboard */
6659         /* only passes fully legal moves                            */
6660         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6661             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6662                                 "False illegal-move claim", GE_XBOARD );
6663         }
6664         return;
6665     }
6666     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6667         /* Program has a broken "time" command that
6668            outputs a string not ending in newline.
6669            Don't use it. */
6670         cps->sendTime = 0;
6671     }
6672     
6673     /*
6674      * If chess program startup fails, exit with an error message.
6675      * Attempts to recover here are futile.
6676      */
6677     if ((StrStr(message, "unknown host") != NULL)
6678         || (StrStr(message, "No remote directory") != NULL)
6679         || (StrStr(message, "not found") != NULL)
6680         || (StrStr(message, "No such file") != NULL)
6681         || (StrStr(message, "can't alloc") != NULL)
6682         || (StrStr(message, "Permission denied") != NULL)) {
6683
6684         cps->maybeThinking = FALSE;
6685         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6686                 cps->which, cps->program, cps->host, message);
6687         RemoveInputSource(cps->isr);
6688         DisplayFatalError(buf1, 0, 1);
6689         return;
6690     }
6691     
6692     /* 
6693      * Look for hint output
6694      */
6695     if (sscanf(message, "Hint: %s", buf1) == 1) {
6696         if (cps == &first && hintRequested) {
6697             hintRequested = FALSE;
6698             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6699                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6700                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6701                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6702                                     fromY, fromX, toY, toX, promoChar, buf1);
6703                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6704                 DisplayInformation(buf2);
6705             } else {
6706                 /* Hint move could not be parsed!? */
6707               snprintf(buf2, sizeof(buf2),
6708                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6709                         buf1, cps->which);
6710                 DisplayError(buf2, 0);
6711             }
6712         } else {
6713             strcpy(lastHint, buf1);
6714         }
6715         return;
6716     }
6717
6718     /*
6719      * Ignore other messages if game is not in progress
6720      */
6721     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6722         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6723
6724     /*
6725      * look for win, lose, draw, or draw offer
6726      */
6727     if (strncmp(message, "1-0", 3) == 0) {
6728         char *p, *q, *r = "";
6729         p = strchr(message, '{');
6730         if (p) {
6731             q = strchr(p, '}');
6732             if (q) {
6733                 *q = NULLCHAR;
6734                 r = p + 1;
6735             }
6736         }
6737         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6738         return;
6739     } else if (strncmp(message, "0-1", 3) == 0) {
6740         char *p, *q, *r = "";
6741         p = strchr(message, '{');
6742         if (p) {
6743             q = strchr(p, '}');
6744             if (q) {
6745                 *q = NULLCHAR;
6746                 r = p + 1;
6747             }
6748         }
6749         /* Kludge for Arasan 4.1 bug */
6750         if (strcmp(r, "Black resigns") == 0) {
6751             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6752             return;
6753         }
6754         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6755         return;
6756     } else if (strncmp(message, "1/2", 3) == 0) {
6757         char *p, *q, *r = "";
6758         p = strchr(message, '{');
6759         if (p) {
6760             q = strchr(p, '}');
6761             if (q) {
6762                 *q = NULLCHAR;
6763                 r = p + 1;
6764             }
6765         }
6766             
6767         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6768         return;
6769
6770     } else if (strncmp(message, "White resign", 12) == 0) {
6771         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6772         return;
6773     } else if (strncmp(message, "Black resign", 12) == 0) {
6774         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6775         return;
6776     } else if (strncmp(message, "White matches", 13) == 0 ||
6777                strncmp(message, "Black matches", 13) == 0   ) {
6778         /* [HGM] ignore GNUShogi noises */
6779         return;
6780     } else if (strncmp(message, "White", 5) == 0 &&
6781                message[5] != '(' &&
6782                StrStr(message, "Black") == NULL) {
6783         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6784         return;
6785     } else if (strncmp(message, "Black", 5) == 0 &&
6786                message[5] != '(') {
6787         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6788         return;
6789     } else if (strcmp(message, "resign") == 0 ||
6790                strcmp(message, "computer resigns") == 0) {
6791         switch (gameMode) {
6792           case MachinePlaysBlack:
6793           case IcsPlayingBlack:
6794             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6795             break;
6796           case MachinePlaysWhite:
6797           case IcsPlayingWhite:
6798             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6799             break;
6800           case TwoMachinesPlay:
6801             if (cps->twoMachinesColor[0] == 'w')
6802               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6803             else
6804               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6805             break;
6806           default:
6807             /* can't happen */
6808             break;
6809         }
6810         return;
6811     } else if (strncmp(message, "opponent mates", 14) == 0) {
6812         switch (gameMode) {
6813           case MachinePlaysBlack:
6814           case IcsPlayingBlack:
6815             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6816             break;
6817           case MachinePlaysWhite:
6818           case IcsPlayingWhite:
6819             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6820             break;
6821           case TwoMachinesPlay:
6822             if (cps->twoMachinesColor[0] == 'w')
6823               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6824             else
6825               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6826             break;
6827           default:
6828             /* can't happen */
6829             break;
6830         }
6831         return;
6832     } else if (strncmp(message, "computer mates", 14) == 0) {
6833         switch (gameMode) {
6834           case MachinePlaysBlack:
6835           case IcsPlayingBlack:
6836             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6837             break;
6838           case MachinePlaysWhite:
6839           case IcsPlayingWhite:
6840             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6841             break;
6842           case TwoMachinesPlay:
6843             if (cps->twoMachinesColor[0] == 'w')
6844               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6845             else
6846               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6847             break;
6848           default:
6849             /* can't happen */
6850             break;
6851         }
6852         return;
6853     } else if (strncmp(message, "checkmate", 9) == 0) {
6854         if (WhiteOnMove(forwardMostMove)) {
6855             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6856         } else {
6857             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6858         }
6859         return;
6860     } else if (strstr(message, "Draw") != NULL ||
6861                strstr(message, "game is a draw") != NULL) {
6862         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6863         return;
6864     } else if (strstr(message, "offer") != NULL &&
6865                strstr(message, "draw") != NULL) {
6866 #if ZIPPY
6867         if (appData.zippyPlay && first.initDone) {
6868             /* Relay offer to ICS */
6869             SendToICS(ics_prefix);
6870             SendToICS("draw\n");
6871         }
6872 #endif
6873         cps->offeredDraw = 2; /* valid until this engine moves twice */
6874         if (gameMode == TwoMachinesPlay) {
6875             if (cps->other->offeredDraw) {
6876                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6877             /* [HGM] in two-machine mode we delay relaying draw offer      */
6878             /* until after we also have move, to see if it is really claim */
6879             }
6880         } else if (gameMode == MachinePlaysWhite ||
6881                    gameMode == MachinePlaysBlack) {
6882           if (userOfferedDraw) {
6883             DisplayInformation(_("Machine accepts your draw offer"));
6884             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6885           } else {
6886             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6887           }
6888         }
6889     }
6890
6891     
6892     /*
6893      * Look for thinking output
6894      */
6895     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6896           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6897                                 ) {
6898         int plylev, mvleft, mvtot, curscore, time;
6899         char mvname[MOVE_LEN];
6900         u64 nodes; // [DM]
6901         char plyext;
6902         int ignore = FALSE;
6903         int prefixHint = FALSE;
6904         mvname[0] = NULLCHAR;
6905
6906         switch (gameMode) {
6907           case MachinePlaysBlack:
6908           case IcsPlayingBlack:
6909             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6910             break;
6911           case MachinePlaysWhite:
6912           case IcsPlayingWhite:
6913             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6914             break;
6915           case AnalyzeMode:
6916           case AnalyzeFile:
6917             break;
6918           case IcsObserving: /* [DM] icsEngineAnalyze */
6919             if (!appData.icsEngineAnalyze) ignore = TRUE;
6920             break;
6921           case TwoMachinesPlay:
6922             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6923                 ignore = TRUE;
6924             }
6925             break;
6926           default:
6927             ignore = TRUE;
6928             break;
6929         }
6930
6931         if (!ignore) {
6932             buf1[0] = NULLCHAR;
6933             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6934                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6935
6936                 if (plyext != ' ' && plyext != '\t') {
6937                     time *= 100;
6938                 }
6939
6940                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6941                 if( cps->scoreIsAbsolute && 
6942                     ( gameMode == MachinePlaysBlack ||
6943                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6944                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6945                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6946                      !WhiteOnMove(currentMove)
6947                     ) )
6948                 {
6949                     curscore = -curscore;
6950                 }
6951
6952
6953                 programStats.depth = plylev;
6954                 programStats.nodes = nodes;
6955                 programStats.time = time;
6956                 programStats.score = curscore;
6957                 programStats.got_only_move = 0;
6958
6959                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6960                         int ticklen;
6961
6962                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6963                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6964                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6965                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6966                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6967                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6968                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6969                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6970                 }
6971
6972                 /* Buffer overflow protection */
6973                 if (buf1[0] != NULLCHAR) {
6974                     if (strlen(buf1) >= sizeof(programStats.movelist)
6975                         && appData.debugMode) {
6976                         fprintf(debugFP,
6977                                 "PV is too long; using the first %u bytes.\n",
6978                                 (unsigned) sizeof(programStats.movelist) - 1);
6979                     }
6980
6981                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6982                 } else {
6983                     sprintf(programStats.movelist, " no PV\n");
6984                 }
6985
6986                 if (programStats.seen_stat) {
6987                     programStats.ok_to_send = 1;
6988                 }
6989
6990                 if (strchr(programStats.movelist, '(') != NULL) {
6991                     programStats.line_is_book = 1;
6992                     programStats.nr_moves = 0;
6993                     programStats.moves_left = 0;
6994                 } else {
6995                     programStats.line_is_book = 0;
6996                 }
6997
6998                 SendProgramStatsToFrontend( cps, &programStats );
6999
7000                 /* 
7001                     [AS] Protect the thinkOutput buffer from overflow... this
7002                     is only useful if buf1 hasn't overflowed first!
7003                 */
7004                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7005                         plylev, 
7006                         (gameMode == TwoMachinesPlay ?
7007                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7008                         ((double) curscore) / 100.0,
7009                         prefixHint ? lastHint : "",
7010                         prefixHint ? " " : "" );
7011
7012                 if( buf1[0] != NULLCHAR ) {
7013                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7014
7015                     if( strlen(buf1) > max_len ) {
7016                         if( appData.debugMode) {
7017                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7018                         }
7019                         buf1[max_len+1] = '\0';
7020                     }
7021
7022                     strcat( thinkOutput, buf1 );
7023                 }
7024
7025                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7026                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7027                     DisplayMove(currentMove - 1);
7028                 }
7029                 return;
7030
7031             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7032                 /* crafty (9.25+) says "(only move) <move>"
7033                  * if there is only 1 legal move
7034                  */
7035                 sscanf(p, "(only move) %s", buf1);
7036                 sprintf(thinkOutput, "%s (only move)", buf1);
7037                 sprintf(programStats.movelist, "%s (only move)", buf1);
7038                 programStats.depth = 1;
7039                 programStats.nr_moves = 1;
7040                 programStats.moves_left = 1;
7041                 programStats.nodes = 1;
7042                 programStats.time = 1;
7043                 programStats.got_only_move = 1;
7044
7045                 /* Not really, but we also use this member to
7046                    mean "line isn't going to change" (Crafty
7047                    isn't searching, so stats won't change) */
7048                 programStats.line_is_book = 1;
7049
7050                 SendProgramStatsToFrontend( cps, &programStats );
7051                 
7052                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7053                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7054                     DisplayMove(currentMove - 1);
7055                 }
7056                 return;
7057             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7058                               &time, &nodes, &plylev, &mvleft,
7059                               &mvtot, mvname) >= 5) {
7060                 /* The stat01: line is from Crafty (9.29+) in response
7061                    to the "." command */
7062                 programStats.seen_stat = 1;
7063                 cps->maybeThinking = TRUE;
7064
7065                 if (programStats.got_only_move || !appData.periodicUpdates)
7066                   return;
7067
7068                 programStats.depth = plylev;
7069                 programStats.time = time;
7070                 programStats.nodes = nodes;
7071                 programStats.moves_left = mvleft;
7072                 programStats.nr_moves = mvtot;
7073                 strcpy(programStats.move_name, mvname);
7074                 programStats.ok_to_send = 1;
7075                 programStats.movelist[0] = '\0';
7076
7077                 SendProgramStatsToFrontend( cps, &programStats );
7078
7079                 return;
7080
7081             } else if (strncmp(message,"++",2) == 0) {
7082                 /* Crafty 9.29+ outputs this */
7083                 programStats.got_fail = 2;
7084                 return;
7085
7086             } else if (strncmp(message,"--",2) == 0) {
7087                 /* Crafty 9.29+ outputs this */
7088                 programStats.got_fail = 1;
7089                 return;
7090
7091             } else if (thinkOutput[0] != NULLCHAR &&
7092                        strncmp(message, "    ", 4) == 0) {
7093                 unsigned message_len;
7094
7095                 p = message;
7096                 while (*p && *p == ' ') p++;
7097
7098                 message_len = strlen( p );
7099
7100                 /* [AS] Avoid buffer overflow */
7101                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7102                     strcat(thinkOutput, " ");
7103                     strcat(thinkOutput, p);
7104                 }
7105
7106                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7107                     strcat(programStats.movelist, " ");
7108                     strcat(programStats.movelist, p);
7109                 }
7110
7111                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7112                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7113                     DisplayMove(currentMove - 1);
7114                 }
7115                 return;
7116             }
7117         }
7118         else {
7119             buf1[0] = NULLCHAR;
7120
7121             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7122                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7123             {
7124                 ChessProgramStats cpstats;
7125
7126                 if (plyext != ' ' && plyext != '\t') {
7127                     time *= 100;
7128                 }
7129
7130                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7131                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7132                     curscore = -curscore;
7133                 }
7134
7135                 cpstats.depth = plylev;
7136                 cpstats.nodes = nodes;
7137                 cpstats.time = time;
7138                 cpstats.score = curscore;
7139                 cpstats.got_only_move = 0;
7140                 cpstats.movelist[0] = '\0';
7141
7142                 if (buf1[0] != NULLCHAR) {
7143                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7144                 }
7145
7146                 cpstats.ok_to_send = 0;
7147                 cpstats.line_is_book = 0;
7148                 cpstats.nr_moves = 0;
7149                 cpstats.moves_left = 0;
7150
7151                 SendProgramStatsToFrontend( cps, &cpstats );
7152             }
7153         }
7154     }
7155 }
7156
7157
7158 /* Parse a game score from the character string "game", and
7159    record it as the history of the current game.  The game
7160    score is NOT assumed to start from the standard position. 
7161    The display is not updated in any way.
7162    */
7163 void
7164 ParseGameHistory(game)
7165      char *game;
7166 {
7167     ChessMove moveType;
7168     int fromX, fromY, toX, toY, boardIndex;
7169     char promoChar;
7170     char *p, *q;
7171     char buf[MSG_SIZ];
7172
7173     if (appData.debugMode)
7174       fprintf(debugFP, "Parsing game history: %s\n", game);
7175
7176     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7177     gameInfo.site = StrSave(appData.icsHost);
7178     gameInfo.date = PGNDate();
7179     gameInfo.round = StrSave("-");
7180
7181     /* Parse out names of players */
7182     while (*game == ' ') game++;
7183     p = buf;
7184     while (*game != ' ') *p++ = *game++;
7185     *p = NULLCHAR;
7186     gameInfo.white = StrSave(buf);
7187     while (*game == ' ') game++;
7188     p = buf;
7189     while (*game != ' ' && *game != '\n') *p++ = *game++;
7190     *p = NULLCHAR;
7191     gameInfo.black = StrSave(buf);
7192
7193     /* Parse moves */
7194     boardIndex = blackPlaysFirst ? 1 : 0;
7195     yynewstr(game);
7196     for (;;) {
7197         yyboardindex = boardIndex;
7198         moveType = (ChessMove) yylex();
7199         switch (moveType) {
7200           case IllegalMove:             /* maybe suicide chess, etc. */
7201   if (appData.debugMode) {
7202     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7203     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7204     setbuf(debugFP, NULL);
7205   }
7206           case WhitePromotionChancellor:
7207           case BlackPromotionChancellor:
7208           case WhitePromotionArchbishop:
7209           case BlackPromotionArchbishop:
7210           case WhitePromotionQueen:
7211           case BlackPromotionQueen:
7212           case WhitePromotionRook:
7213           case BlackPromotionRook:
7214           case WhitePromotionBishop:
7215           case BlackPromotionBishop:
7216           case WhitePromotionKnight:
7217           case BlackPromotionKnight:
7218           case WhitePromotionKing:
7219           case BlackPromotionKing:
7220           case NormalMove:
7221           case WhiteCapturesEnPassant:
7222           case BlackCapturesEnPassant:
7223           case WhiteKingSideCastle:
7224           case WhiteQueenSideCastle:
7225           case BlackKingSideCastle:
7226           case BlackQueenSideCastle:
7227           case WhiteKingSideCastleWild:
7228           case WhiteQueenSideCastleWild:
7229           case BlackKingSideCastleWild:
7230           case BlackQueenSideCastleWild:
7231           /* PUSH Fabien */
7232           case WhiteHSideCastleFR:
7233           case WhiteASideCastleFR:
7234           case BlackHSideCastleFR:
7235           case BlackASideCastleFR:
7236           /* POP Fabien */
7237             fromX = currentMoveString[0] - AAA;
7238             fromY = currentMoveString[1] - ONE;
7239             toX = currentMoveString[2] - AAA;
7240             toY = currentMoveString[3] - ONE;
7241             promoChar = currentMoveString[4];
7242             break;
7243           case WhiteDrop:
7244           case BlackDrop:
7245             fromX = moveType == WhiteDrop ?
7246               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7247             (int) CharToPiece(ToLower(currentMoveString[0]));
7248             fromY = DROP_RANK;
7249             toX = currentMoveString[2] - AAA;
7250             toY = currentMoveString[3] - ONE;
7251             promoChar = NULLCHAR;
7252             break;
7253           case AmbiguousMove:
7254             /* bug? */
7255             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7256   if (appData.debugMode) {
7257     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7258     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7259     setbuf(debugFP, NULL);
7260   }
7261             DisplayError(buf, 0);
7262             return;
7263           case ImpossibleMove:
7264             /* bug? */
7265             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7266   if (appData.debugMode) {
7267     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7268     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7269     setbuf(debugFP, NULL);
7270   }
7271             DisplayError(buf, 0);
7272             return;
7273           case (ChessMove) 0:   /* end of file */
7274             if (boardIndex < backwardMostMove) {
7275                 /* Oops, gap.  How did that happen? */
7276                 DisplayError(_("Gap in move list"), 0);
7277                 return;
7278             }
7279             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7280             if (boardIndex > forwardMostMove) {
7281                 forwardMostMove = boardIndex;
7282             }
7283             return;
7284           case ElapsedTime:
7285             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7286                 strcat(parseList[boardIndex-1], " ");
7287                 strcat(parseList[boardIndex-1], yy_text);
7288             }
7289             continue;
7290           case Comment:
7291           case PGNTag:
7292           case NAG:
7293           default:
7294             /* ignore */
7295             continue;
7296           case WhiteWins:
7297           case BlackWins:
7298           case GameIsDrawn:
7299           case GameUnfinished:
7300             if (gameMode == IcsExamining) {
7301                 if (boardIndex < backwardMostMove) {
7302                     /* Oops, gap.  How did that happen? */
7303                     return;
7304                 }
7305                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7306                 return;
7307             }
7308             gameInfo.result = moveType;
7309             p = strchr(yy_text, '{');
7310             if (p == NULL) p = strchr(yy_text, '(');
7311             if (p == NULL) {
7312                 p = yy_text;
7313                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7314             } else {
7315                 q = strchr(p, *p == '{' ? '}' : ')');
7316                 if (q != NULL) *q = NULLCHAR;
7317                 p++;
7318             }
7319             gameInfo.resultDetails = StrSave(p);
7320             continue;
7321         }
7322         if (boardIndex >= forwardMostMove &&
7323             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7324             backwardMostMove = blackPlaysFirst ? 1 : 0;
7325             return;
7326         }
7327         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7328                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7329                                  parseList[boardIndex]);
7330         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7331         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7332         /* currentMoveString is set as a side-effect of yylex */
7333         strcpy(moveList[boardIndex], currentMoveString);
7334         strcat(moveList[boardIndex], "\n");
7335         boardIndex++;
7336         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7337                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7338         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7339                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7340           case MT_NONE:
7341           case MT_STALEMATE:
7342           default:
7343             break;
7344           case MT_CHECK:
7345             if(gameInfo.variant != VariantShogi)
7346                 strcat(parseList[boardIndex - 1], "+");
7347             break;
7348           case MT_CHECKMATE:
7349           case MT_STAINMATE:
7350             strcat(parseList[boardIndex - 1], "#");
7351             break;
7352         }
7353     }
7354 }
7355
7356
7357 /* Apply a move to the given board  */
7358 void
7359 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7360      int fromX, fromY, toX, toY;
7361      int promoChar;
7362      Board board;
7363      char *castling;
7364      char *ep;
7365 {
7366   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7367
7368     /* [HGM] compute & store e.p. status and castling rights for new position */
7369     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7370     { int i;
7371
7372       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7373       oldEP = *ep;
7374       *ep = EP_NONE;
7375
7376       if( board[toY][toX] != EmptySquare ) 
7377            *ep = EP_CAPTURE;  
7378
7379       if( board[fromY][fromX] == WhitePawn ) {
7380            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7381                *ep = EP_PAWN_MOVE;
7382            if( toY-fromY==2) {
7383                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7384                         gameInfo.variant != VariantBerolina || toX < fromX)
7385                       *ep = toX | berolina;
7386                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7387                         gameInfo.variant != VariantBerolina || toX > fromX) 
7388                       *ep = toX;
7389            }
7390       } else 
7391       if( board[fromY][fromX] == BlackPawn ) {
7392            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7393                *ep = EP_PAWN_MOVE; 
7394            if( toY-fromY== -2) {
7395                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7396                         gameInfo.variant != VariantBerolina || toX < fromX)
7397                       *ep = toX | berolina;
7398                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7399                         gameInfo.variant != VariantBerolina || toX > fromX) 
7400                       *ep = toX;
7401            }
7402        }
7403
7404        for(i=0; i<nrCastlingRights; i++) {
7405            if(castling[i] == fromX && castlingRank[i] == fromY ||
7406               castling[i] == toX   && castlingRank[i] == toY   
7407              ) castling[i] = -1; // revoke for moved or captured piece
7408        }
7409
7410     }
7411
7412   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7413   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7414        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7415          
7416   if (fromX == toX && fromY == toY) return;
7417
7418   if (fromY == DROP_RANK) {
7419         /* must be first */
7420         piece = board[toY][toX] = (ChessSquare) fromX;
7421   } else {
7422      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7423      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7424      if(gameInfo.variant == VariantKnightmate)
7425          king += (int) WhiteUnicorn - (int) WhiteKing;
7426
7427     /* Code added by Tord: */
7428     /* FRC castling assumed when king captures friendly rook. */
7429     if (board[fromY][fromX] == WhiteKing &&
7430              board[toY][toX] == WhiteRook) {
7431       board[fromY][fromX] = EmptySquare;
7432       board[toY][toX] = EmptySquare;
7433       if(toX > fromX) {
7434         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7435       } else {
7436         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7437       }
7438     } else if (board[fromY][fromX] == BlackKing &&
7439                board[toY][toX] == BlackRook) {
7440       board[fromY][fromX] = EmptySquare;
7441       board[toY][toX] = EmptySquare;
7442       if(toX > fromX) {
7443         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7444       } else {
7445         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7446       }
7447     /* End of code added by Tord */
7448
7449     } else if (board[fromY][fromX] == king
7450         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7451         && toY == fromY && toX > fromX+1) {
7452         board[fromY][fromX] = EmptySquare;
7453         board[toY][toX] = king;
7454         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7455         board[fromY][BOARD_RGHT-1] = EmptySquare;
7456     } else if (board[fromY][fromX] == king
7457         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7458                && toY == fromY && toX < fromX-1) {
7459         board[fromY][fromX] = EmptySquare;
7460         board[toY][toX] = king;
7461         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7462         board[fromY][BOARD_LEFT] = EmptySquare;
7463     } else if (board[fromY][fromX] == WhitePawn
7464                && toY == BOARD_HEIGHT-1
7465                && gameInfo.variant != VariantXiangqi
7466                ) {
7467         /* white pawn promotion */
7468         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7469         if (board[toY][toX] == EmptySquare) {
7470             board[toY][toX] = WhiteQueen;
7471         }
7472         if(gameInfo.variant==VariantBughouse ||
7473            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7474             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7475         board[fromY][fromX] = EmptySquare;
7476     } else if ((fromY == BOARD_HEIGHT-4)
7477                && (toX != fromX)
7478                && gameInfo.variant != VariantXiangqi
7479                && gameInfo.variant != VariantBerolina
7480                && (board[fromY][fromX] == WhitePawn)
7481                && (board[toY][toX] == EmptySquare)) {
7482         board[fromY][fromX] = EmptySquare;
7483         board[toY][toX] = WhitePawn;
7484         captured = board[toY - 1][toX];
7485         board[toY - 1][toX] = EmptySquare;
7486     } else if ((fromY == BOARD_HEIGHT-4)
7487                && (toX == fromX)
7488                && gameInfo.variant == VariantBerolina
7489                && (board[fromY][fromX] == WhitePawn)
7490                && (board[toY][toX] == EmptySquare)) {
7491         board[fromY][fromX] = EmptySquare;
7492         board[toY][toX] = WhitePawn;
7493         if(oldEP & EP_BEROLIN_A) {
7494                 captured = board[fromY][fromX-1];
7495                 board[fromY][fromX-1] = EmptySquare;
7496         }else{  captured = board[fromY][fromX+1];
7497                 board[fromY][fromX+1] = EmptySquare;
7498         }
7499     } else if (board[fromY][fromX] == king
7500         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7501                && toY == fromY && toX > fromX+1) {
7502         board[fromY][fromX] = EmptySquare;
7503         board[toY][toX] = king;
7504         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7505         board[fromY][BOARD_RGHT-1] = EmptySquare;
7506     } else if (board[fromY][fromX] == king
7507         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7508                && toY == fromY && toX < fromX-1) {
7509         board[fromY][fromX] = EmptySquare;
7510         board[toY][toX] = king;
7511         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7512         board[fromY][BOARD_LEFT] = EmptySquare;
7513     } else if (fromY == 7 && fromX == 3
7514                && board[fromY][fromX] == BlackKing
7515                && toY == 7 && toX == 5) {
7516         board[fromY][fromX] = EmptySquare;
7517         board[toY][toX] = BlackKing;
7518         board[fromY][7] = EmptySquare;
7519         board[toY][4] = BlackRook;
7520     } else if (fromY == 7 && fromX == 3
7521                && board[fromY][fromX] == BlackKing
7522                && toY == 7 && toX == 1) {
7523         board[fromY][fromX] = EmptySquare;
7524         board[toY][toX] = BlackKing;
7525         board[fromY][0] = EmptySquare;
7526         board[toY][2] = BlackRook;
7527     } else if (board[fromY][fromX] == BlackPawn
7528                && toY == 0
7529                && gameInfo.variant != VariantXiangqi
7530                ) {
7531         /* black pawn promotion */
7532         board[0][toX] = CharToPiece(ToLower(promoChar));
7533         if (board[0][toX] == EmptySquare) {
7534             board[0][toX] = BlackQueen;
7535         }
7536         if(gameInfo.variant==VariantBughouse ||
7537            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7538             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7539         board[fromY][fromX] = EmptySquare;
7540     } else if ((fromY == 3)
7541                && (toX != fromX)
7542                && gameInfo.variant != VariantXiangqi
7543                && gameInfo.variant != VariantBerolina
7544                && (board[fromY][fromX] == BlackPawn)
7545                && (board[toY][toX] == EmptySquare)) {
7546         board[fromY][fromX] = EmptySquare;
7547         board[toY][toX] = BlackPawn;
7548         captured = board[toY + 1][toX];
7549         board[toY + 1][toX] = EmptySquare;
7550     } else if ((fromY == 3)
7551                && (toX == fromX)
7552                && gameInfo.variant == VariantBerolina
7553                && (board[fromY][fromX] == BlackPawn)
7554                && (board[toY][toX] == EmptySquare)) {
7555         board[fromY][fromX] = EmptySquare;
7556         board[toY][toX] = BlackPawn;
7557         if(oldEP & EP_BEROLIN_A) {
7558                 captured = board[fromY][fromX-1];
7559                 board[fromY][fromX-1] = EmptySquare;
7560         }else{  captured = board[fromY][fromX+1];
7561                 board[fromY][fromX+1] = EmptySquare;
7562         }
7563     } else {
7564         board[toY][toX] = board[fromY][fromX];
7565         board[fromY][fromX] = EmptySquare;
7566     }
7567
7568     /* [HGM] now we promote for Shogi, if needed */
7569     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7570         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7571   }
7572
7573     if (gameInfo.holdingsWidth != 0) {
7574
7575       /* !!A lot more code needs to be written to support holdings  */
7576       /* [HGM] OK, so I have written it. Holdings are stored in the */
7577       /* penultimate board files, so they are automaticlly stored   */
7578       /* in the game history.                                       */
7579       if (fromY == DROP_RANK) {
7580         /* Delete from holdings, by decreasing count */
7581         /* and erasing image if necessary            */
7582         p = (int) fromX;
7583         if(p < (int) BlackPawn) { /* white drop */
7584              p -= (int)WhitePawn;
7585                  p = PieceToNumber((ChessSquare)p);
7586              if(p >= gameInfo.holdingsSize) p = 0;
7587              if(--board[p][BOARD_WIDTH-2] <= 0)
7588                   board[p][BOARD_WIDTH-1] = EmptySquare;
7589              if((int)board[p][BOARD_WIDTH-2] < 0)
7590                         board[p][BOARD_WIDTH-2] = 0;
7591         } else {                  /* black drop */
7592              p -= (int)BlackPawn;
7593                  p = PieceToNumber((ChessSquare)p);
7594              if(p >= gameInfo.holdingsSize) p = 0;
7595              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7596                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7597              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7598                         board[BOARD_HEIGHT-1-p][1] = 0;
7599         }
7600       }
7601       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7602           && gameInfo.variant != VariantBughouse        ) {
7603         /* [HGM] holdings: Add to holdings, if holdings exist */
7604         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7605                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7606                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7607         }
7608         p = (int) captured;
7609         if (p >= (int) BlackPawn) {
7610           p -= (int)BlackPawn;
7611           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7612                   /* in Shogi restore piece to its original  first */
7613                   captured = (ChessSquare) (DEMOTED captured);
7614                   p = DEMOTED p;
7615           }
7616           p = PieceToNumber((ChessSquare)p);
7617           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7618           board[p][BOARD_WIDTH-2]++;
7619           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7620         } else {
7621           p -= (int)WhitePawn;
7622           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7623                   captured = (ChessSquare) (DEMOTED captured);
7624                   p = DEMOTED p;
7625           }
7626           p = PieceToNumber((ChessSquare)p);
7627           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7628           board[BOARD_HEIGHT-1-p][1]++;
7629           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7630         }
7631       }
7632     } else if (gameInfo.variant == VariantAtomic) {
7633       if (captured != EmptySquare) {
7634         int y, x;
7635         for (y = toY-1; y <= toY+1; y++) {
7636           for (x = toX-1; x <= toX+1; x++) {
7637             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7638                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7639               board[y][x] = EmptySquare;
7640             }
7641           }
7642         }
7643         board[toY][toX] = EmptySquare;
7644       }
7645     }
7646     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7647         /* [HGM] Shogi promotions */
7648         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7649     }
7650
7651     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7652                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7653         // [HGM] superchess: take promotion piece out of holdings
7654         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7655         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7656             if(!--board[k][BOARD_WIDTH-2])
7657                 board[k][BOARD_WIDTH-1] = EmptySquare;
7658         } else {
7659             if(!--board[BOARD_HEIGHT-1-k][1])
7660                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7661         }
7662     }
7663
7664 }
7665
7666 /* Updates forwardMostMove */
7667 void
7668 MakeMove(fromX, fromY, toX, toY, promoChar)
7669      int fromX, fromY, toX, toY;
7670      int promoChar;
7671 {
7672 //    forwardMostMove++; // [HGM] bare: moved downstream
7673
7674     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7675         int timeLeft; static int lastLoadFlag=0; int king, piece;
7676         piece = boards[forwardMostMove][fromY][fromX];
7677         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7678         if(gameInfo.variant == VariantKnightmate)
7679             king += (int) WhiteUnicorn - (int) WhiteKing;
7680         if(forwardMostMove == 0) {
7681             if(blackPlaysFirst) 
7682                 fprintf(serverMoves, "%s;", second.tidy);
7683             fprintf(serverMoves, "%s;", first.tidy);
7684             if(!blackPlaysFirst) 
7685                 fprintf(serverMoves, "%s;", second.tidy);
7686         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7687         lastLoadFlag = loadFlag;
7688         // print base move
7689         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7690         // print castling suffix
7691         if( toY == fromY && piece == king ) {
7692             if(toX-fromX > 1)
7693                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7694             if(fromX-toX >1)
7695                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7696         }
7697         // e.p. suffix
7698         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7699              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7700              boards[forwardMostMove][toY][toX] == EmptySquare
7701              && fromX != toX )
7702                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7703         // promotion suffix
7704         if(promoChar != NULLCHAR)
7705                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7706         if(!loadFlag) {
7707             fprintf(serverMoves, "/%d/%d",
7708                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7709             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7710             else                      timeLeft = blackTimeRemaining/1000;
7711             fprintf(serverMoves, "/%d", timeLeft);
7712         }
7713         fflush(serverMoves);
7714     }
7715
7716     if (forwardMostMove+1 >= MAX_MOVES) {
7717       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7718                         0, 1);
7719       return;
7720     }
7721     if (commentList[forwardMostMove+1] != NULL) {
7722         free(commentList[forwardMostMove+1]);
7723         commentList[forwardMostMove+1] = NULL;
7724     }
7725     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7726     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7727     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7728                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7729     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7730     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7731     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7732     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7733     gameInfo.result = GameUnfinished;
7734     if (gameInfo.resultDetails != NULL) {
7735         free(gameInfo.resultDetails);
7736         gameInfo.resultDetails = NULL;
7737     }
7738     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7739                               moveList[forwardMostMove - 1]);
7740     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7741                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7742                              fromY, fromX, toY, toX, promoChar,
7743                              parseList[forwardMostMove - 1]);
7744     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7745                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7746                             castlingRights[forwardMostMove]) ) {
7747       case MT_NONE:
7748       case MT_STALEMATE:
7749       default:
7750         break;
7751       case MT_CHECK:
7752         if(gameInfo.variant != VariantShogi)
7753             strcat(parseList[forwardMostMove - 1], "+");
7754         break;
7755       case MT_CHECKMATE:
7756       case MT_STAINMATE:
7757         strcat(parseList[forwardMostMove - 1], "#");
7758         break;
7759     }
7760     if (appData.debugMode) {
7761         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7762     }
7763
7764 }
7765
7766 /* Updates currentMove if not pausing */
7767 void
7768 ShowMove(fromX, fromY, toX, toY)
7769 {
7770     int instant = (gameMode == PlayFromGameFile) ?
7771         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7772     if(appData.noGUI) return;
7773     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7774         if (!instant) {
7775             if (forwardMostMove == currentMove + 1) {
7776                 AnimateMove(boards[forwardMostMove - 1],
7777                             fromX, fromY, toX, toY);
7778             }
7779             if (appData.highlightLastMove) {
7780                 SetHighlights(fromX, fromY, toX, toY);
7781             }
7782         }
7783         currentMove = forwardMostMove;
7784     }
7785
7786     if (instant) return;
7787
7788     DisplayMove(currentMove - 1);
7789     DrawPosition(FALSE, boards[currentMove]);
7790     DisplayBothClocks();
7791     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7792 }
7793
7794 void SendEgtPath(ChessProgramState *cps)
7795 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7796         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7797
7798         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7799
7800         while(*p) {
7801             char c, *q = name+1, *r, *s;
7802
7803             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7804             while(*p && *p != ',') *q++ = *p++;
7805             *q++ = ':'; *q = 0;
7806             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7807                 strcmp(name, ",nalimov:") == 0 ) {
7808                 // take nalimov path from the menu-changeable option first, if it is defined
7809                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7810                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7811             } else
7812             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7813                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7814                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7815                 s = r = StrStr(s, ":") + 1; // beginning of path info
7816                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7817                 c = *r; *r = 0;             // temporarily null-terminate path info
7818                     *--q = 0;               // strip of trailig ':' from name
7819                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7820                 *r = c;
7821                 SendToProgram(buf,cps);     // send egtbpath command for this format
7822             }
7823             if(*p == ',') p++; // read away comma to position for next format name
7824         }
7825 }
7826
7827 void
7828 InitChessProgram(cps, setup)
7829      ChessProgramState *cps;
7830      int setup; /* [HGM] needed to setup FRC opening position */
7831 {
7832     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7833     if (appData.noChessProgram) return;
7834     hintRequested = FALSE;
7835     bookRequested = FALSE;
7836
7837     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7838     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7839     if(cps->memSize) { /* [HGM] memory */
7840         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7841         SendToProgram(buf, cps);
7842     }
7843     SendEgtPath(cps); /* [HGM] EGT */
7844     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7845         sprintf(buf, "cores %d\n", appData.smpCores);
7846         SendToProgram(buf, cps);
7847     }
7848
7849     SendToProgram(cps->initString, cps);
7850     if (gameInfo.variant != VariantNormal &&
7851         gameInfo.variant != VariantLoadable
7852         /* [HGM] also send variant if board size non-standard */
7853         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7854                                             ) {
7855       char *v = VariantName(gameInfo.variant);
7856       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7857         /* [HGM] in protocol 1 we have to assume all variants valid */
7858         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7859         DisplayFatalError(buf, 0, 1);
7860         return;
7861       }
7862
7863       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7864       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7865       if( gameInfo.variant == VariantXiangqi )
7866            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7867       if( gameInfo.variant == VariantShogi )
7868            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7869       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7870            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7871       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7872                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7873            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7874       if( gameInfo.variant == VariantCourier )
7875            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7876       if( gameInfo.variant == VariantSuper )
7877            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7878       if( gameInfo.variant == VariantGreat )
7879            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7880
7881       if(overruled) {
7882            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7883                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7884            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7885            if(StrStr(cps->variants, b) == NULL) { 
7886                // specific sized variant not known, check if general sizing allowed
7887                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7888                    if(StrStr(cps->variants, "boardsize") == NULL) {
7889                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7890                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7891                        DisplayFatalError(buf, 0, 1);
7892                        return;
7893                    }
7894                    /* [HGM] here we really should compare with the maximum supported board size */
7895                }
7896            }
7897       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7898       sprintf(buf, "variant %s\n", b);
7899       SendToProgram(buf, cps);
7900     }
7901     currentlyInitializedVariant = gameInfo.variant;
7902
7903     /* [HGM] send opening position in FRC to first engine */
7904     if(setup) {
7905           SendToProgram("force\n", cps);
7906           SendBoard(cps, 0);
7907           /* engine is now in force mode! Set flag to wake it up after first move. */
7908           setboardSpoiledMachineBlack = 1;
7909     }
7910
7911     if (cps->sendICS) {
7912       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7913       SendToProgram(buf, cps);
7914     }
7915     cps->maybeThinking = FALSE;
7916     cps->offeredDraw = 0;
7917     if (!appData.icsActive) {
7918         SendTimeControl(cps, movesPerSession, timeControl,
7919                         timeIncrement, appData.searchDepth,
7920                         searchTime);
7921     }
7922     if (appData.showThinking 
7923         // [HGM] thinking: four options require thinking output to be sent
7924         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7925                                 ) {
7926         SendToProgram("post\n", cps);
7927     }
7928     SendToProgram("hard\n", cps);
7929     if (!appData.ponderNextMove) {
7930         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7931            it without being sure what state we are in first.  "hard"
7932            is not a toggle, so that one is OK.
7933          */
7934         SendToProgram("easy\n", cps);
7935     }
7936     if (cps->usePing) {
7937       sprintf(buf, "ping %d\n", ++cps->lastPing);
7938       SendToProgram(buf, cps);
7939     }
7940     cps->initDone = TRUE;
7941 }   
7942
7943
7944 void
7945 StartChessProgram(cps)
7946      ChessProgramState *cps;
7947 {
7948     char buf[MSG_SIZ];
7949     int err;
7950
7951     if (appData.noChessProgram) return;
7952     cps->initDone = FALSE;
7953
7954     if (strcmp(cps->host, "localhost") == 0) {
7955         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7956     } else if (*appData.remoteShell == NULLCHAR) {
7957         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7958     } else {
7959         if (*appData.remoteUser == NULLCHAR) {
7960           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7961                     cps->program);
7962         } else {
7963           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7964                     cps->host, appData.remoteUser, cps->program);
7965         }
7966         err = StartChildProcess(buf, "", &cps->pr);
7967     }
7968     
7969     if (err != 0) {
7970         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7971         DisplayFatalError(buf, err, 1);
7972         cps->pr = NoProc;
7973         cps->isr = NULL;
7974         return;
7975     }
7976     
7977     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7978     if (cps->protocolVersion > 1) {
7979       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7980       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7981       cps->comboCnt = 0;  //                and values of combo boxes
7982       SendToProgram(buf, cps);
7983     } else {
7984       SendToProgram("xboard\n", cps);
7985     }
7986 }
7987
7988
7989 void
7990 TwoMachinesEventIfReady P((void))
7991 {
7992   if (first.lastPing != first.lastPong) {
7993     DisplayMessage("", _("Waiting for first chess program"));
7994     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7995     return;
7996   }
7997   if (second.lastPing != second.lastPong) {
7998     DisplayMessage("", _("Waiting for second chess program"));
7999     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8000     return;
8001   }
8002   ThawUI();
8003   TwoMachinesEvent();
8004 }
8005
8006 void
8007 NextMatchGame P((void))
8008 {
8009     int index; /* [HGM] autoinc: step load index during match */
8010     Reset(FALSE, TRUE);
8011     if (*appData.loadGameFile != NULLCHAR) {
8012         index = appData.loadGameIndex;
8013         if(index < 0) { // [HGM] autoinc
8014             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8015             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8016         } 
8017         LoadGameFromFile(appData.loadGameFile,
8018                          index,
8019                          appData.loadGameFile, FALSE);
8020     } else if (*appData.loadPositionFile != NULLCHAR) {
8021         index = appData.loadPositionIndex;
8022         if(index < 0) { // [HGM] autoinc
8023             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8024             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8025         } 
8026         LoadPositionFromFile(appData.loadPositionFile,
8027                              index,
8028                              appData.loadPositionFile);
8029     }
8030     TwoMachinesEventIfReady();
8031 }
8032
8033 void UserAdjudicationEvent( int result )
8034 {
8035     ChessMove gameResult = GameIsDrawn;
8036
8037     if( result > 0 ) {
8038         gameResult = WhiteWins;
8039     }
8040     else if( result < 0 ) {
8041         gameResult = BlackWins;
8042     }
8043
8044     if( gameMode == TwoMachinesPlay ) {
8045         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8046     }
8047 }
8048
8049
8050 // [HGM] save: calculate checksum of game to make games easily identifiable
8051 int StringCheckSum(char *s)
8052 {
8053         int i = 0;
8054         if(s==NULL) return 0;
8055         while(*s) i = i*259 + *s++;
8056         return i;
8057 }
8058
8059 int GameCheckSum()
8060 {
8061         int i, sum=0;
8062         for(i=backwardMostMove; i<forwardMostMove; i++) {
8063                 sum += pvInfoList[i].depth;
8064                 sum += StringCheckSum(parseList[i]);
8065                 sum += StringCheckSum(commentList[i]);
8066                 sum *= 261;
8067         }
8068         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8069         return sum + StringCheckSum(commentList[i]);
8070 } // end of save patch
8071
8072 void
8073 GameEnds(result, resultDetails, whosays)
8074      ChessMove result;
8075      char *resultDetails;
8076      int whosays;
8077 {
8078     GameMode nextGameMode;
8079     int isIcsGame;
8080     char buf[MSG_SIZ];
8081
8082     if(endingGame) return; /* [HGM] crash: forbid recursion */
8083     endingGame = 1;
8084
8085     if (appData.debugMode) {
8086       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8087               result, resultDetails ? resultDetails : "(null)", whosays);
8088     }
8089
8090     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8091         /* If we are playing on ICS, the server decides when the
8092            game is over, but the engine can offer to draw, claim 
8093            a draw, or resign. 
8094          */
8095 #if ZIPPY
8096         if (appData.zippyPlay && first.initDone) {
8097             if (result == GameIsDrawn) {
8098                 /* In case draw still needs to be claimed */
8099                 SendToICS(ics_prefix);
8100                 SendToICS("draw\n");
8101             } else if (StrCaseStr(resultDetails, "resign")) {
8102                 SendToICS(ics_prefix);
8103                 SendToICS("resign\n");
8104             }
8105         }
8106 #endif
8107         endingGame = 0; /* [HGM] crash */
8108         return;
8109     }
8110
8111     /* If we're loading the game from a file, stop */
8112     if (whosays == GE_FILE) {
8113       (void) StopLoadGameTimer();
8114       gameFileFP = NULL;
8115     }
8116
8117     /* Cancel draw offers */
8118     first.offeredDraw = second.offeredDraw = 0;
8119
8120     /* If this is an ICS game, only ICS can really say it's done;
8121        if not, anyone can. */
8122     isIcsGame = (gameMode == IcsPlayingWhite || 
8123                  gameMode == IcsPlayingBlack || 
8124                  gameMode == IcsObserving    || 
8125                  gameMode == IcsExamining);
8126
8127     if (!isIcsGame || whosays == GE_ICS) {
8128         /* OK -- not an ICS game, or ICS said it was done */
8129         StopClocks();
8130         if (!isIcsGame && !appData.noChessProgram) 
8131           SetUserThinkingEnables();
8132     
8133         /* [HGM] if a machine claims the game end we verify this claim */
8134         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8135             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8136                 char claimer;
8137                 ChessMove trueResult = (ChessMove) -1;
8138
8139                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8140                                             first.twoMachinesColor[0] :
8141                                             second.twoMachinesColor[0] ;
8142
8143                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8144                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8145                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8146                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8147                 } else
8148                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8149                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8150                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8151                 } else
8152                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8153                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8154                 }
8155
8156                 // now verify win claims, but not in drop games, as we don't understand those yet
8157                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8158                                                  || gameInfo.variant == VariantGreat) &&
8159                     (result == WhiteWins && claimer == 'w' ||
8160                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8161                       if (appData.debugMode) {
8162                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8163                                 result, epStatus[forwardMostMove], forwardMostMove);
8164                       }
8165                       if(result != trueResult) {
8166                               sprintf(buf, "False win claim: '%s'", resultDetails);
8167                               result = claimer == 'w' ? BlackWins : WhiteWins;
8168                               resultDetails = buf;
8169                       }
8170                 } else
8171                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8172                     && (forwardMostMove <= backwardMostMove ||
8173                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8174                         (claimer=='b')==(forwardMostMove&1))
8175                                                                                   ) {
8176                       /* [HGM] verify: draws that were not flagged are false claims */
8177                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8178                       result = claimer == 'w' ? BlackWins : WhiteWins;
8179                       resultDetails = buf;
8180                 }
8181                 /* (Claiming a loss is accepted no questions asked!) */
8182             }
8183             /* [HGM] bare: don't allow bare King to win */
8184             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8185                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8186                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8187                && result != GameIsDrawn)
8188             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8189                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8190                         int p = (int)boards[forwardMostMove][i][j] - color;
8191                         if(p >= 0 && p <= (int)WhiteKing) k++;
8192                 }
8193                 if (appData.debugMode) {
8194                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8195                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8196                 }
8197                 if(k <= 1) {
8198                         result = GameIsDrawn;
8199                         sprintf(buf, "%s but bare king", resultDetails);
8200                         resultDetails = buf;
8201                 }
8202             }
8203         }
8204
8205
8206         if(serverMoves != NULL && !loadFlag) { char c = '=';
8207             if(result==WhiteWins) c = '+';
8208             if(result==BlackWins) c = '-';
8209             if(resultDetails != NULL)
8210                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8211         }
8212         if (resultDetails != NULL) {
8213             gameInfo.result = result;
8214             gameInfo.resultDetails = StrSave(resultDetails);
8215
8216             /* display last move only if game was not loaded from file */
8217             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8218                 DisplayMove(currentMove - 1);
8219     
8220             if (forwardMostMove != 0) {
8221                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8222                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8223                                                                 ) {
8224                     if (*appData.saveGameFile != NULLCHAR) {
8225                         SaveGameToFile(appData.saveGameFile, TRUE);
8226                     } else if (appData.autoSaveGames) {
8227                         AutoSaveGame();
8228                     }
8229                     if (*appData.savePositionFile != NULLCHAR) {
8230                         SavePositionToFile(appData.savePositionFile);
8231                     }
8232                 }
8233             }
8234
8235             /* Tell program how game ended in case it is learning */
8236             /* [HGM] Moved this to after saving the PGN, just in case */
8237             /* engine died and we got here through time loss. In that */
8238             /* case we will get a fatal error writing the pipe, which */
8239             /* would otherwise lose us the PGN.                       */
8240             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8241             /* output during GameEnds should never be fatal anymore   */
8242             if (gameMode == MachinePlaysWhite ||
8243                 gameMode == MachinePlaysBlack ||
8244                 gameMode == TwoMachinesPlay ||
8245                 gameMode == IcsPlayingWhite ||
8246                 gameMode == IcsPlayingBlack ||
8247                 gameMode == BeginningOfGame) {
8248                 char buf[MSG_SIZ];
8249                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8250                         resultDetails);
8251                 if (first.pr != NoProc) {
8252                     SendToProgram(buf, &first);
8253                 }
8254                 if (second.pr != NoProc &&
8255                     gameMode == TwoMachinesPlay) {
8256                     SendToProgram(buf, &second);
8257                 }
8258             }
8259         }
8260
8261         if (appData.icsActive) {
8262             if (appData.quietPlay &&
8263                 (gameMode == IcsPlayingWhite ||
8264                  gameMode == IcsPlayingBlack)) {
8265                 SendToICS(ics_prefix);
8266                 SendToICS("set shout 1\n");
8267             }
8268             nextGameMode = IcsIdle;
8269             ics_user_moved = FALSE;
8270             /* clean up premove.  It's ugly when the game has ended and the
8271              * premove highlights are still on the board.
8272              */
8273             if (gotPremove) {
8274               gotPremove = FALSE;
8275               ClearPremoveHighlights();
8276               DrawPosition(FALSE, boards[currentMove]);
8277             }
8278             if (whosays == GE_ICS) {
8279                 switch (result) {
8280                 case WhiteWins:
8281                     if (gameMode == IcsPlayingWhite)
8282                         PlayIcsWinSound();
8283                     else if(gameMode == IcsPlayingBlack)
8284                         PlayIcsLossSound();
8285                     break;
8286                 case BlackWins:
8287                     if (gameMode == IcsPlayingBlack)
8288                         PlayIcsWinSound();
8289                     else if(gameMode == IcsPlayingWhite)
8290                         PlayIcsLossSound();
8291                     break;
8292                 case GameIsDrawn:
8293                     PlayIcsDrawSound();
8294                     break;
8295                 default:
8296                     PlayIcsUnfinishedSound();
8297                 }
8298             }
8299         } else if (gameMode == EditGame ||
8300                    gameMode == PlayFromGameFile || 
8301                    gameMode == AnalyzeMode || 
8302                    gameMode == AnalyzeFile) {
8303             nextGameMode = gameMode;
8304         } else {
8305             nextGameMode = EndOfGame;
8306         }
8307         pausing = FALSE;
8308         ModeHighlight();
8309     } else {
8310         nextGameMode = gameMode;
8311     }
8312
8313     if (appData.noChessProgram) {
8314         gameMode = nextGameMode;
8315         ModeHighlight();
8316         endingGame = 0; /* [HGM] crash */
8317         return;
8318     }
8319
8320     if (first.reuse) {
8321         /* Put first chess program into idle state */
8322         if (first.pr != NoProc &&
8323             (gameMode == MachinePlaysWhite ||
8324              gameMode == MachinePlaysBlack ||
8325              gameMode == TwoMachinesPlay ||
8326              gameMode == IcsPlayingWhite ||
8327              gameMode == IcsPlayingBlack ||
8328              gameMode == BeginningOfGame)) {
8329             SendToProgram("force\n", &first);
8330             if (first.usePing) {
8331               char buf[MSG_SIZ];
8332               sprintf(buf, "ping %d\n", ++first.lastPing);
8333               SendToProgram(buf, &first);
8334             }
8335         }
8336     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8337         /* Kill off first chess program */
8338         if (first.isr != NULL)
8339           RemoveInputSource(first.isr);
8340         first.isr = NULL;
8341     
8342         if (first.pr != NoProc) {
8343             ExitAnalyzeMode();
8344             DoSleep( appData.delayBeforeQuit );
8345             SendToProgram("quit\n", &first);
8346             DoSleep( appData.delayAfterQuit );
8347             DestroyChildProcess(first.pr, first.useSigterm);
8348         }
8349         first.pr = NoProc;
8350     }
8351     if (second.reuse) {
8352         /* Put second chess program into idle state */
8353         if (second.pr != NoProc &&
8354             gameMode == TwoMachinesPlay) {
8355             SendToProgram("force\n", &second);
8356             if (second.usePing) {
8357               char buf[MSG_SIZ];
8358               sprintf(buf, "ping %d\n", ++second.lastPing);
8359               SendToProgram(buf, &second);
8360             }
8361         }
8362     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8363         /* Kill off second chess program */
8364         if (second.isr != NULL)
8365           RemoveInputSource(second.isr);
8366         second.isr = NULL;
8367     
8368         if (second.pr != NoProc) {
8369             DoSleep( appData.delayBeforeQuit );
8370             SendToProgram("quit\n", &second);
8371             DoSleep( appData.delayAfterQuit );
8372             DestroyChildProcess(second.pr, second.useSigterm);
8373         }
8374         second.pr = NoProc;
8375     }
8376
8377     if (matchMode && gameMode == TwoMachinesPlay) {
8378         switch (result) {
8379         case WhiteWins:
8380           if (first.twoMachinesColor[0] == 'w') {
8381             first.matchWins++;
8382           } else {
8383             second.matchWins++;
8384           }
8385           break;
8386         case BlackWins:
8387           if (first.twoMachinesColor[0] == 'b') {
8388             first.matchWins++;
8389           } else {
8390             second.matchWins++;
8391           }
8392           break;
8393         default:
8394           break;
8395         }
8396         if (matchGame < appData.matchGames) {
8397             char *tmp;
8398             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8399                 tmp = first.twoMachinesColor;
8400                 first.twoMachinesColor = second.twoMachinesColor;
8401                 second.twoMachinesColor = tmp;
8402             }
8403             gameMode = nextGameMode;
8404             matchGame++;
8405             if(appData.matchPause>10000 || appData.matchPause<10)
8406                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8407             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8408             endingGame = 0; /* [HGM] crash */
8409             return;
8410         } else {
8411             char buf[MSG_SIZ];
8412             gameMode = nextGameMode;
8413             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8414                     first.tidy, second.tidy,
8415                     first.matchWins, second.matchWins,
8416                     appData.matchGames - (first.matchWins + second.matchWins));
8417             DisplayFatalError(buf, 0, 0);
8418         }
8419     }
8420     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8421         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8422       ExitAnalyzeMode();
8423     gameMode = nextGameMode;
8424     ModeHighlight();
8425     endingGame = 0;  /* [HGM] crash */
8426 }
8427
8428 /* Assumes program was just initialized (initString sent).
8429    Leaves program in force mode. */
8430 void
8431 FeedMovesToProgram(cps, upto) 
8432      ChessProgramState *cps;
8433      int upto;
8434 {
8435     int i;
8436     
8437     if (appData.debugMode)
8438       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8439               startedFromSetupPosition ? "position and " : "",
8440               backwardMostMove, upto, cps->which);
8441     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8442         // [HGM] variantswitch: make engine aware of new variant
8443         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8444                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8445         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8446         SendToProgram(buf, cps);
8447         currentlyInitializedVariant = gameInfo.variant;
8448     }
8449     SendToProgram("force\n", cps);
8450     if (startedFromSetupPosition) {
8451         SendBoard(cps, backwardMostMove);
8452     if (appData.debugMode) {
8453         fprintf(debugFP, "feedMoves\n");
8454     }
8455     }
8456     for (i = backwardMostMove; i < upto; i++) {
8457         SendMoveToProgram(i, cps);
8458     }
8459 }
8460
8461
8462 void
8463 ResurrectChessProgram()
8464 {
8465      /* The chess program may have exited.
8466         If so, restart it and feed it all the moves made so far. */
8467
8468     if (appData.noChessProgram || first.pr != NoProc) return;
8469     
8470     StartChessProgram(&first);
8471     InitChessProgram(&first, FALSE);
8472     FeedMovesToProgram(&first, currentMove);
8473
8474     if (!first.sendTime) {
8475         /* can't tell gnuchess what its clock should read,
8476            so we bow to its notion. */
8477         ResetClocks();
8478         timeRemaining[0][currentMove] = whiteTimeRemaining;
8479         timeRemaining[1][currentMove] = blackTimeRemaining;
8480     }
8481
8482     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8483                 appData.icsEngineAnalyze) && first.analysisSupport) {
8484       SendToProgram("analyze\n", &first);
8485       first.analyzing = TRUE;
8486     }
8487 }
8488
8489 /*
8490  * Button procedures
8491  */
8492 void
8493 Reset(redraw, init)
8494      int redraw, init;
8495 {
8496     int i;
8497
8498     if (appData.debugMode) {
8499         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8500                 redraw, init, gameMode);
8501     }
8502     pausing = pauseExamInvalid = FALSE;
8503     startedFromSetupPosition = blackPlaysFirst = FALSE;
8504     firstMove = TRUE;
8505     whiteFlag = blackFlag = FALSE;
8506     userOfferedDraw = FALSE;
8507     hintRequested = bookRequested = FALSE;
8508     first.maybeThinking = FALSE;
8509     second.maybeThinking = FALSE;
8510     first.bookSuspend = FALSE; // [HGM] book
8511     second.bookSuspend = FALSE;
8512     thinkOutput[0] = NULLCHAR;
8513     lastHint[0] = NULLCHAR;
8514     ClearGameInfo(&gameInfo);
8515     gameInfo.variant = StringToVariant(appData.variant);
8516     ics_user_moved = ics_clock_paused = FALSE;
8517     ics_getting_history = H_FALSE;
8518     ics_gamenum = -1;
8519     white_holding[0] = black_holding[0] = NULLCHAR;
8520     ClearProgramStats();
8521     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8522     
8523     ResetFrontEnd();
8524     ClearHighlights();
8525     flipView = appData.flipView;
8526     ClearPremoveHighlights();
8527     gotPremove = FALSE;
8528     alarmSounded = FALSE;
8529
8530     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8531     if(appData.serverMovesName != NULL) {
8532         /* [HGM] prepare to make moves file for broadcasting */
8533         clock_t t = clock();
8534         if(serverMoves != NULL) fclose(serverMoves);
8535         serverMoves = fopen(appData.serverMovesName, "r");
8536         if(serverMoves != NULL) {
8537             fclose(serverMoves);
8538             /* delay 15 sec before overwriting, so all clients can see end */
8539             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8540         }
8541         serverMoves = fopen(appData.serverMovesName, "w");
8542     }
8543
8544     ExitAnalyzeMode();
8545     gameMode = BeginningOfGame;
8546     ModeHighlight();
8547     if(appData.icsActive) gameInfo.variant = VariantNormal;
8548     currentMove = forwardMostMove = backwardMostMove = 0;
8549     InitPosition(redraw);
8550     for (i = 0; i < MAX_MOVES; i++) {
8551         if (commentList[i] != NULL) {
8552             free(commentList[i]);
8553             commentList[i] = NULL;
8554         }
8555     }
8556     ResetClocks();
8557     timeRemaining[0][0] = whiteTimeRemaining;
8558     timeRemaining[1][0] = blackTimeRemaining;
8559     if (first.pr == NULL) {
8560         StartChessProgram(&first);
8561     }
8562     if (init) {
8563             InitChessProgram(&first, startedFromSetupPosition);
8564     }
8565     DisplayTitle("");
8566     DisplayMessage("", "");
8567     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8568     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8569 }
8570
8571 void
8572 AutoPlayGameLoop()
8573 {
8574     for (;;) {
8575         if (!AutoPlayOneMove())
8576           return;
8577         if (matchMode || appData.timeDelay == 0)
8578           continue;
8579         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8580           return;
8581         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8582         break;
8583     }
8584 }
8585
8586
8587 int
8588 AutoPlayOneMove()
8589 {
8590     int fromX, fromY, toX, toY;
8591
8592     if (appData.debugMode) {
8593       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8594     }
8595
8596     if (gameMode != PlayFromGameFile)
8597       return FALSE;
8598
8599     if (currentMove >= forwardMostMove) {
8600       gameMode = EditGame;
8601       ModeHighlight();
8602
8603       /* [AS] Clear current move marker at the end of a game */
8604       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8605
8606       return FALSE;
8607     }
8608     
8609     toX = moveList[currentMove][2] - AAA;
8610     toY = moveList[currentMove][3] - ONE;
8611
8612     if (moveList[currentMove][1] == '@') {
8613         if (appData.highlightLastMove) {
8614             SetHighlights(-1, -1, toX, toY);
8615         }
8616     } else {
8617         fromX = moveList[currentMove][0] - AAA;
8618         fromY = moveList[currentMove][1] - ONE;
8619
8620         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8621
8622         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8623
8624         if (appData.highlightLastMove) {
8625             SetHighlights(fromX, fromY, toX, toY);
8626         }
8627     }
8628     DisplayMove(currentMove);
8629     SendMoveToProgram(currentMove++, &first);
8630     DisplayBothClocks();
8631     DrawPosition(FALSE, boards[currentMove]);
8632     // [HGM] PV info: always display, routine tests if empty
8633     DisplayComment(currentMove - 1, commentList[currentMove]);
8634     return TRUE;
8635 }
8636
8637
8638 int
8639 LoadGameOneMove(readAhead)
8640      ChessMove readAhead;
8641 {
8642     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8643     char promoChar = NULLCHAR;
8644     ChessMove moveType;
8645     char move[MSG_SIZ];
8646     char *p, *q;
8647     
8648     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8649         gameMode != AnalyzeMode && gameMode != Training) {
8650         gameFileFP = NULL;
8651         return FALSE;
8652     }
8653     
8654     yyboardindex = forwardMostMove;
8655     if (readAhead != (ChessMove)0) {
8656       moveType = readAhead;
8657     } else {
8658       if (gameFileFP == NULL)
8659           return FALSE;
8660       moveType = (ChessMove) yylex();
8661     }
8662     
8663     done = FALSE;
8664     switch (moveType) {
8665       case Comment:
8666         if (appData.debugMode) 
8667           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8668         p = yy_text;
8669         if (*p == '{' || *p == '[' || *p == '(') {
8670             p[strlen(p) - 1] = NULLCHAR;
8671             p++;
8672         }
8673
8674         /* append the comment but don't display it */
8675         while (*p == '\n') p++;
8676         AppendComment(currentMove, p);
8677         return TRUE;
8678
8679       case WhiteCapturesEnPassant:
8680       case BlackCapturesEnPassant:
8681       case WhitePromotionChancellor:
8682       case BlackPromotionChancellor:
8683       case WhitePromotionArchbishop:
8684       case BlackPromotionArchbishop:
8685       case WhitePromotionCentaur:
8686       case BlackPromotionCentaur:
8687       case WhitePromotionQueen:
8688       case BlackPromotionQueen:
8689       case WhitePromotionRook:
8690       case BlackPromotionRook:
8691       case WhitePromotionBishop:
8692       case BlackPromotionBishop:
8693       case WhitePromotionKnight:
8694       case BlackPromotionKnight:
8695       case WhitePromotionKing:
8696       case BlackPromotionKing:
8697       case NormalMove:
8698       case WhiteKingSideCastle:
8699       case WhiteQueenSideCastle:
8700       case BlackKingSideCastle:
8701       case BlackQueenSideCastle:
8702       case WhiteKingSideCastleWild:
8703       case WhiteQueenSideCastleWild:
8704       case BlackKingSideCastleWild:
8705       case BlackQueenSideCastleWild:
8706       /* PUSH Fabien */
8707       case WhiteHSideCastleFR:
8708       case WhiteASideCastleFR:
8709       case BlackHSideCastleFR:
8710       case BlackASideCastleFR:
8711       /* POP Fabien */
8712         if (appData.debugMode)
8713           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8714         fromX = currentMoveString[0] - AAA;
8715         fromY = currentMoveString[1] - ONE;
8716         toX = currentMoveString[2] - AAA;
8717         toY = currentMoveString[3] - ONE;
8718         promoChar = currentMoveString[4];
8719         break;
8720
8721       case WhiteDrop:
8722       case BlackDrop:
8723         if (appData.debugMode)
8724           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8725         fromX = moveType == WhiteDrop ?
8726           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8727         (int) CharToPiece(ToLower(currentMoveString[0]));
8728         fromY = DROP_RANK;
8729         toX = currentMoveString[2] - AAA;
8730         toY = currentMoveString[3] - ONE;
8731         break;
8732
8733       case WhiteWins:
8734       case BlackWins:
8735       case GameIsDrawn:
8736       case GameUnfinished:
8737         if (appData.debugMode)
8738           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8739         p = strchr(yy_text, '{');
8740         if (p == NULL) p = strchr(yy_text, '(');
8741         if (p == NULL) {
8742             p = yy_text;
8743             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8744         } else {
8745             q = strchr(p, *p == '{' ? '}' : ')');
8746             if (q != NULL) *q = NULLCHAR;
8747             p++;
8748         }
8749         GameEnds(moveType, p, GE_FILE);
8750         done = TRUE;
8751         if (cmailMsgLoaded) {
8752             ClearHighlights();
8753             flipView = WhiteOnMove(currentMove);
8754             if (moveType == GameUnfinished) flipView = !flipView;
8755             if (appData.debugMode)
8756               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8757         }
8758         break;
8759
8760       case (ChessMove) 0:       /* end of file */
8761         if (appData.debugMode)
8762           fprintf(debugFP, "Parser hit end of file\n");
8763         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8764                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8765           case MT_NONE:
8766           case MT_CHECK:
8767             break;
8768           case MT_CHECKMATE:
8769           case MT_STAINMATE:
8770             if (WhiteOnMove(currentMove)) {
8771                 GameEnds(BlackWins, "Black mates", GE_FILE);
8772             } else {
8773                 GameEnds(WhiteWins, "White mates", GE_FILE);
8774             }
8775             break;
8776           case MT_STALEMATE:
8777             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8778             break;
8779         }
8780         done = TRUE;
8781         break;
8782
8783       case MoveNumberOne:
8784         if (lastLoadGameStart == GNUChessGame) {
8785             /* GNUChessGames have numbers, but they aren't move numbers */
8786             if (appData.debugMode)
8787               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8788                       yy_text, (int) moveType);
8789             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8790         }
8791         /* else fall thru */
8792
8793       case XBoardGame:
8794       case GNUChessGame:
8795       case PGNTag:
8796         /* Reached start of next game in file */
8797         if (appData.debugMode)
8798           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8799         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8800                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8801           case MT_NONE:
8802           case MT_CHECK:
8803             break;
8804           case MT_CHECKMATE:
8805           case MT_STAINMATE:
8806             if (WhiteOnMove(currentMove)) {
8807                 GameEnds(BlackWins, "Black mates", GE_FILE);
8808             } else {
8809                 GameEnds(WhiteWins, "White mates", GE_FILE);
8810             }
8811             break;
8812           case MT_STALEMATE:
8813             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8814             break;
8815         }
8816         done = TRUE;
8817         break;
8818
8819       case PositionDiagram:     /* should not happen; ignore */
8820       case ElapsedTime:         /* ignore */
8821       case NAG:                 /* ignore */
8822         if (appData.debugMode)
8823           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8824                   yy_text, (int) moveType);
8825         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8826
8827       case IllegalMove:
8828         if (appData.testLegality) {
8829             if (appData.debugMode)
8830               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8831             sprintf(move, _("Illegal move: %d.%s%s"),
8832                     (forwardMostMove / 2) + 1,
8833                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8834             DisplayError(move, 0);
8835             done = TRUE;
8836         } else {
8837             if (appData.debugMode)
8838               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8839                       yy_text, currentMoveString);
8840             fromX = currentMoveString[0] - AAA;
8841             fromY = currentMoveString[1] - ONE;
8842             toX = currentMoveString[2] - AAA;
8843             toY = currentMoveString[3] - ONE;
8844             promoChar = currentMoveString[4];
8845         }
8846         break;
8847
8848       case AmbiguousMove:
8849         if (appData.debugMode)
8850           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8851         sprintf(move, _("Ambiguous move: %d.%s%s"),
8852                 (forwardMostMove / 2) + 1,
8853                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8854         DisplayError(move, 0);
8855         done = TRUE;
8856         break;
8857
8858       default:
8859       case ImpossibleMove:
8860         if (appData.debugMode)
8861           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8862         sprintf(move, _("Illegal move: %d.%s%s"),
8863                 (forwardMostMove / 2) + 1,
8864                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8865         DisplayError(move, 0);
8866         done = TRUE;
8867         break;
8868     }
8869
8870     if (done) {
8871         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8872             DrawPosition(FALSE, boards[currentMove]);
8873             DisplayBothClocks();
8874             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8875               DisplayComment(currentMove - 1, commentList[currentMove]);
8876         }
8877         (void) StopLoadGameTimer();
8878         gameFileFP = NULL;
8879         cmailOldMove = forwardMostMove;
8880         return FALSE;
8881     } else {
8882         /* currentMoveString is set as a side-effect of yylex */
8883         strcat(currentMoveString, "\n");
8884         strcpy(moveList[forwardMostMove], currentMoveString);
8885         
8886         thinkOutput[0] = NULLCHAR;
8887         MakeMove(fromX, fromY, toX, toY, promoChar);
8888         currentMove = forwardMostMove;
8889         return TRUE;
8890     }
8891 }
8892
8893 /* Load the nth game from the given file */
8894 int
8895 LoadGameFromFile(filename, n, title, useList)
8896      char *filename;
8897      int n;
8898      char *title;
8899      /*Boolean*/ int useList;
8900 {
8901     FILE *f;
8902     char buf[MSG_SIZ];
8903
8904     if (strcmp(filename, "-") == 0) {
8905         f = stdin;
8906         title = "stdin";
8907     } else {
8908         f = fopen(filename, "rb");
8909         if (f == NULL) {
8910           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8911             DisplayError(buf, errno);
8912             return FALSE;
8913         }
8914     }
8915     if (fseek(f, 0, 0) == -1) {
8916         /* f is not seekable; probably a pipe */
8917         useList = FALSE;
8918     }
8919     if (useList && n == 0) {
8920         int error = GameListBuild(f);
8921         if (error) {
8922             DisplayError(_("Cannot build game list"), error);
8923         } else if (!ListEmpty(&gameList) &&
8924                    ((ListGame *) gameList.tailPred)->number > 1) {
8925             GameListPopUp(f, title);
8926             return TRUE;
8927         }
8928         GameListDestroy();
8929         n = 1;
8930     }
8931     if (n == 0) n = 1;
8932     return LoadGame(f, n, title, FALSE);
8933 }
8934
8935
8936 void
8937 MakeRegisteredMove()
8938 {
8939     int fromX, fromY, toX, toY;
8940     char promoChar;
8941     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8942         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8943           case CMAIL_MOVE:
8944           case CMAIL_DRAW:
8945             if (appData.debugMode)
8946               fprintf(debugFP, "Restoring %s for game %d\n",
8947                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8948     
8949             thinkOutput[0] = NULLCHAR;
8950             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8951             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8952             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8953             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8954             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8955             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8956             MakeMove(fromX, fromY, toX, toY, promoChar);
8957             ShowMove(fromX, fromY, toX, toY);
8958               
8959             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8960                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8961               case MT_NONE:
8962               case MT_CHECK:
8963                 break;
8964                 
8965               case MT_CHECKMATE:
8966               case MT_STAINMATE:
8967                 if (WhiteOnMove(currentMove)) {
8968                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8969                 } else {
8970                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8971                 }
8972                 break;
8973                 
8974               case MT_STALEMATE:
8975                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8976                 break;
8977             }
8978
8979             break;
8980             
8981           case CMAIL_RESIGN:
8982             if (WhiteOnMove(currentMove)) {
8983                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8984             } else {
8985                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8986             }
8987             break;
8988             
8989           case CMAIL_ACCEPT:
8990             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8991             break;
8992               
8993           default:
8994             break;
8995         }
8996     }
8997
8998     return;
8999 }
9000
9001 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9002 int
9003 CmailLoadGame(f, gameNumber, title, useList)
9004      FILE *f;
9005      int gameNumber;
9006      char *title;
9007      int useList;
9008 {
9009     int retVal;
9010
9011     if (gameNumber > nCmailGames) {
9012         DisplayError(_("No more games in this message"), 0);
9013         return FALSE;
9014     }
9015     if (f == lastLoadGameFP) {
9016         int offset = gameNumber - lastLoadGameNumber;
9017         if (offset == 0) {
9018             cmailMsg[0] = NULLCHAR;
9019             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9020                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9021                 nCmailMovesRegistered--;
9022             }
9023             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9024             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9025                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9026             }
9027         } else {
9028             if (! RegisterMove()) return FALSE;
9029         }
9030     }
9031
9032     retVal = LoadGame(f, gameNumber, title, useList);
9033
9034     /* Make move registered during previous look at this game, if any */
9035     MakeRegisteredMove();
9036
9037     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9038         commentList[currentMove]
9039           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9040         DisplayComment(currentMove - 1, commentList[currentMove]);
9041     }
9042
9043     return retVal;
9044 }
9045
9046 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9047 int
9048 ReloadGame(offset)
9049      int offset;
9050 {
9051     int gameNumber = lastLoadGameNumber + offset;
9052     if (lastLoadGameFP == NULL) {
9053         DisplayError(_("No game has been loaded yet"), 0);
9054         return FALSE;
9055     }
9056     if (gameNumber <= 0) {
9057         DisplayError(_("Can't back up any further"), 0);
9058         return FALSE;
9059     }
9060     if (cmailMsgLoaded) {
9061         return CmailLoadGame(lastLoadGameFP, gameNumber,
9062                              lastLoadGameTitle, lastLoadGameUseList);
9063     } else {
9064         return LoadGame(lastLoadGameFP, gameNumber,
9065                         lastLoadGameTitle, lastLoadGameUseList);
9066     }
9067 }
9068
9069
9070
9071 /* Load the nth game from open file f */
9072 int
9073 LoadGame(f, gameNumber, title, useList)
9074      FILE *f;
9075      int gameNumber;
9076      char *title;
9077      int useList;
9078 {
9079     ChessMove cm;
9080     char buf[MSG_SIZ];
9081     int gn = gameNumber;
9082     ListGame *lg = NULL;
9083     int numPGNTags = 0;
9084     int err;
9085     GameMode oldGameMode;
9086     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9087
9088     if (appData.debugMode) 
9089         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9090
9091     if (gameMode == Training )
9092         SetTrainingModeOff();
9093
9094     oldGameMode = gameMode;
9095     if (gameMode != BeginningOfGame) {
9096       Reset(FALSE, TRUE);
9097     }
9098
9099     gameFileFP = f;
9100     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9101         fclose(lastLoadGameFP);
9102     }
9103
9104     if (useList) {
9105         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9106         
9107         if (lg) {
9108             fseek(f, lg->offset, 0);
9109             GameListHighlight(gameNumber);
9110             gn = 1;
9111         }
9112         else {
9113             DisplayError(_("Game number out of range"), 0);
9114             return FALSE;
9115         }
9116     } else {
9117         GameListDestroy();
9118         if (fseek(f, 0, 0) == -1) {
9119             if (f == lastLoadGameFP ?
9120                 gameNumber == lastLoadGameNumber + 1 :
9121                 gameNumber == 1) {
9122                 gn = 1;
9123             } else {
9124                 DisplayError(_("Can't seek on game file"), 0);
9125                 return FALSE;
9126             }
9127         }
9128     }
9129     lastLoadGameFP = f;
9130     lastLoadGameNumber = gameNumber;
9131     strcpy(lastLoadGameTitle, title);
9132     lastLoadGameUseList = useList;
9133
9134     yynewfile(f);
9135
9136     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9137       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9138                 lg->gameInfo.black);
9139             DisplayTitle(buf);
9140     } else if (*title != NULLCHAR) {
9141         if (gameNumber > 1) {
9142             sprintf(buf, "%s %d", title, gameNumber);
9143             DisplayTitle(buf);
9144         } else {
9145             DisplayTitle(title);
9146         }
9147     }
9148
9149     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9150         gameMode = PlayFromGameFile;
9151         ModeHighlight();
9152     }
9153
9154     currentMove = forwardMostMove = backwardMostMove = 0;
9155     CopyBoard(boards[0], initialPosition);
9156     StopClocks();
9157
9158     /*
9159      * Skip the first gn-1 games in the file.
9160      * Also skip over anything that precedes an identifiable 
9161      * start of game marker, to avoid being confused by 
9162      * garbage at the start of the file.  Currently 
9163      * recognized start of game markers are the move number "1",
9164      * the pattern "gnuchess .* game", the pattern
9165      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9166      * A game that starts with one of the latter two patterns
9167      * will also have a move number 1, possibly
9168      * following a position diagram.
9169      * 5-4-02: Let's try being more lenient and allowing a game to
9170      * start with an unnumbered move.  Does that break anything?
9171      */
9172     cm = lastLoadGameStart = (ChessMove) 0;
9173     while (gn > 0) {
9174         yyboardindex = forwardMostMove;
9175         cm = (ChessMove) yylex();
9176         switch (cm) {
9177           case (ChessMove) 0:
9178             if (cmailMsgLoaded) {
9179                 nCmailGames = CMAIL_MAX_GAMES - gn;
9180             } else {
9181                 Reset(TRUE, TRUE);
9182                 DisplayError(_("Game not found in file"), 0);
9183             }
9184             return FALSE;
9185
9186           case GNUChessGame:
9187           case XBoardGame:
9188             gn--;
9189             lastLoadGameStart = cm;
9190             break;
9191             
9192           case MoveNumberOne:
9193             switch (lastLoadGameStart) {
9194               case GNUChessGame:
9195               case XBoardGame:
9196               case PGNTag:
9197                 break;
9198               case MoveNumberOne:
9199               case (ChessMove) 0:
9200                 gn--;           /* count this game */
9201                 lastLoadGameStart = cm;
9202                 break;
9203               default:
9204                 /* impossible */
9205                 break;
9206             }
9207             break;
9208
9209           case PGNTag:
9210             switch (lastLoadGameStart) {
9211               case GNUChessGame:
9212               case PGNTag:
9213               case MoveNumberOne:
9214               case (ChessMove) 0:
9215                 gn--;           /* count this game */
9216                 lastLoadGameStart = cm;
9217                 break;
9218               case XBoardGame:
9219                 lastLoadGameStart = cm; /* game counted already */
9220                 break;
9221               default:
9222                 /* impossible */
9223                 break;
9224             }
9225             if (gn > 0) {
9226                 do {
9227                     yyboardindex = forwardMostMove;
9228                     cm = (ChessMove) yylex();
9229                 } while (cm == PGNTag || cm == Comment);
9230             }
9231             break;
9232
9233           case WhiteWins:
9234           case BlackWins:
9235           case GameIsDrawn:
9236             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9237                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9238                     != CMAIL_OLD_RESULT) {
9239                     nCmailResults ++ ;
9240                     cmailResult[  CMAIL_MAX_GAMES
9241                                 - gn - 1] = CMAIL_OLD_RESULT;
9242                 }
9243             }
9244             break;
9245
9246           case NormalMove:
9247             /* Only a NormalMove can be at the start of a game
9248              * without a position diagram. */
9249             if (lastLoadGameStart == (ChessMove) 0) {
9250               gn--;
9251               lastLoadGameStart = MoveNumberOne;
9252             }
9253             break;
9254
9255           default:
9256             break;
9257         }
9258     }
9259     
9260     if (appData.debugMode)
9261       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9262
9263     if (cm == XBoardGame) {
9264         /* Skip any header junk before position diagram and/or move 1 */
9265         for (;;) {
9266             yyboardindex = forwardMostMove;
9267             cm = (ChessMove) yylex();
9268
9269             if (cm == (ChessMove) 0 ||
9270                 cm == GNUChessGame || cm == XBoardGame) {
9271                 /* Empty game; pretend end-of-file and handle later */
9272                 cm = (ChessMove) 0;
9273                 break;
9274             }
9275
9276             if (cm == MoveNumberOne || cm == PositionDiagram ||
9277                 cm == PGNTag || cm == Comment)
9278               break;
9279         }
9280     } else if (cm == GNUChessGame) {
9281         if (gameInfo.event != NULL) {
9282             free(gameInfo.event);
9283         }
9284         gameInfo.event = StrSave(yy_text);
9285     }   
9286
9287     startedFromSetupPosition = FALSE;
9288     while (cm == PGNTag) {
9289         if (appData.debugMode) 
9290           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9291         err = ParsePGNTag(yy_text, &gameInfo);
9292         if (!err) numPGNTags++;
9293
9294         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9295         if(gameInfo.variant != oldVariant) {
9296             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9297             InitPosition(TRUE);
9298             oldVariant = gameInfo.variant;
9299             if (appData.debugMode) 
9300               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9301         }
9302
9303
9304         if (gameInfo.fen != NULL) {
9305           Board initial_position;
9306           startedFromSetupPosition = TRUE;
9307           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9308             Reset(TRUE, TRUE);
9309             DisplayError(_("Bad FEN position in file"), 0);
9310             return FALSE;
9311           }
9312           CopyBoard(boards[0], initial_position);
9313           if (blackPlaysFirst) {
9314             currentMove = forwardMostMove = backwardMostMove = 1;
9315             CopyBoard(boards[1], initial_position);
9316             strcpy(moveList[0], "");
9317             strcpy(parseList[0], "");
9318             timeRemaining[0][1] = whiteTimeRemaining;
9319             timeRemaining[1][1] = blackTimeRemaining;
9320             if (commentList[0] != NULL) {
9321               commentList[1] = commentList[0];
9322               commentList[0] = NULL;
9323             }
9324           } else {
9325             currentMove = forwardMostMove = backwardMostMove = 0;
9326           }
9327           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9328           {   int i;
9329               initialRulePlies = FENrulePlies;
9330               epStatus[forwardMostMove] = FENepStatus;
9331               for( i=0; i< nrCastlingRights; i++ )
9332                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9333           }
9334           yyboardindex = forwardMostMove;
9335           free(gameInfo.fen);
9336           gameInfo.fen = NULL;
9337         }
9338
9339         yyboardindex = forwardMostMove;
9340         cm = (ChessMove) yylex();
9341
9342         /* Handle comments interspersed among the tags */
9343         while (cm == Comment) {
9344             char *p;
9345             if (appData.debugMode) 
9346               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9347             p = yy_text;
9348             if (*p == '{' || *p == '[' || *p == '(') {
9349                 p[strlen(p) - 1] = NULLCHAR;
9350                 p++;
9351             }
9352             while (*p == '\n') p++;
9353             AppendComment(currentMove, p);
9354             yyboardindex = forwardMostMove;
9355             cm = (ChessMove) yylex();
9356         }
9357     }
9358
9359     /* don't rely on existence of Event tag since if game was
9360      * pasted from clipboard the Event tag may not exist
9361      */
9362     if (numPGNTags > 0){
9363         char *tags;
9364         if (gameInfo.variant == VariantNormal) {
9365           gameInfo.variant = StringToVariant(gameInfo.event);
9366         }
9367         if (!matchMode) {
9368           if( appData.autoDisplayTags ) {
9369             tags = PGNTags(&gameInfo);
9370             TagsPopUp(tags, CmailMsg());
9371             free(tags);
9372           }
9373         }
9374     } else {
9375         /* Make something up, but don't display it now */
9376         SetGameInfo();
9377         TagsPopDown();
9378     }
9379
9380     if (cm == PositionDiagram) {
9381         int i, j;
9382         char *p;
9383         Board initial_position;
9384
9385         if (appData.debugMode)
9386           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9387
9388         if (!startedFromSetupPosition) {
9389             p = yy_text;
9390             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9391               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9392                 switch (*p) {
9393                   case '[':
9394                   case '-':
9395                   case ' ':
9396                   case '\t':
9397                   case '\n':
9398                   case '\r':
9399                     break;
9400                   default:
9401                     initial_position[i][j++] = CharToPiece(*p);
9402                     break;
9403                 }
9404             while (*p == ' ' || *p == '\t' ||
9405                    *p == '\n' || *p == '\r') p++;
9406         
9407             if (strncmp(p, "black", strlen("black"))==0)
9408               blackPlaysFirst = TRUE;
9409             else
9410               blackPlaysFirst = FALSE;
9411             startedFromSetupPosition = TRUE;
9412         
9413             CopyBoard(boards[0], initial_position);
9414             if (blackPlaysFirst) {
9415                 currentMove = forwardMostMove = backwardMostMove = 1;
9416                 CopyBoard(boards[1], initial_position);
9417                 strcpy(moveList[0], "");
9418                 strcpy(parseList[0], "");
9419                 timeRemaining[0][1] = whiteTimeRemaining;
9420                 timeRemaining[1][1] = blackTimeRemaining;
9421                 if (commentList[0] != NULL) {
9422                     commentList[1] = commentList[0];
9423                     commentList[0] = NULL;
9424                 }
9425             } else {
9426                 currentMove = forwardMostMove = backwardMostMove = 0;
9427             }
9428         }
9429         yyboardindex = forwardMostMove;
9430         cm = (ChessMove) yylex();
9431     }
9432
9433     if (first.pr == NoProc) {
9434         StartChessProgram(&first);
9435     }
9436     InitChessProgram(&first, FALSE);
9437     SendToProgram("force\n", &first);
9438     if (startedFromSetupPosition) {
9439         SendBoard(&first, forwardMostMove);
9440     if (appData.debugMode) {
9441         fprintf(debugFP, "Load Game\n");
9442     }
9443         DisplayBothClocks();
9444     }      
9445
9446     /* [HGM] server: flag to write setup moves in broadcast file as one */
9447     loadFlag = appData.suppressLoadMoves;
9448
9449     while (cm == Comment) {
9450         char *p;
9451         if (appData.debugMode) 
9452           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9453         p = yy_text;
9454         if (*p == '{' || *p == '[' || *p == '(') {
9455             p[strlen(p) - 1] = NULLCHAR;
9456             p++;
9457         }
9458         while (*p == '\n') p++;
9459         AppendComment(currentMove, p);
9460         yyboardindex = forwardMostMove;
9461         cm = (ChessMove) yylex();
9462     }
9463
9464     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9465         cm == WhiteWins || cm == BlackWins ||
9466         cm == GameIsDrawn || cm == GameUnfinished) {
9467         DisplayMessage("", _("No moves in game"));
9468         if (cmailMsgLoaded) {
9469             if (appData.debugMode)
9470               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9471             ClearHighlights();
9472             flipView = FALSE;
9473         }
9474         DrawPosition(FALSE, boards[currentMove]);
9475         DisplayBothClocks();
9476         gameMode = EditGame;
9477         ModeHighlight();
9478         gameFileFP = NULL;
9479         cmailOldMove = 0;
9480         return TRUE;
9481     }
9482
9483     // [HGM] PV info: routine tests if comment empty
9484     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9485         DisplayComment(currentMove - 1, commentList[currentMove]);
9486     }
9487     if (!matchMode && appData.timeDelay != 0) 
9488       DrawPosition(FALSE, boards[currentMove]);
9489
9490     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9491       programStats.ok_to_send = 1;
9492     }
9493
9494     /* if the first token after the PGN tags is a move
9495      * and not move number 1, retrieve it from the parser 
9496      */
9497     if (cm != MoveNumberOne)
9498         LoadGameOneMove(cm);
9499
9500     /* load the remaining moves from the file */
9501     while (LoadGameOneMove((ChessMove)0)) {
9502       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9503       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9504     }
9505
9506     /* rewind to the start of the game */
9507     currentMove = backwardMostMove;
9508
9509     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9510
9511     if (oldGameMode == AnalyzeFile ||
9512         oldGameMode == AnalyzeMode) {
9513       AnalyzeFileEvent();
9514     }
9515
9516     if (matchMode || appData.timeDelay == 0) {
9517       ToEndEvent();
9518       gameMode = EditGame;
9519       ModeHighlight();
9520     } else if (appData.timeDelay > 0) {
9521       AutoPlayGameLoop();
9522     }
9523
9524     if (appData.debugMode) 
9525         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9526
9527     loadFlag = 0; /* [HGM] true game starts */
9528     return TRUE;
9529 }
9530
9531 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9532 int
9533 ReloadPosition(offset)
9534      int offset;
9535 {
9536     int positionNumber = lastLoadPositionNumber + offset;
9537     if (lastLoadPositionFP == NULL) {
9538         DisplayError(_("No position has been loaded yet"), 0);
9539         return FALSE;
9540     }
9541     if (positionNumber <= 0) {
9542         DisplayError(_("Can't back up any further"), 0);
9543         return FALSE;
9544     }
9545     return LoadPosition(lastLoadPositionFP, positionNumber,
9546                         lastLoadPositionTitle);
9547 }
9548
9549 /* Load the nth position from the given file */
9550 int
9551 LoadPositionFromFile(filename, n, title)
9552      char *filename;
9553      int n;
9554      char *title;
9555 {
9556     FILE *f;
9557     char buf[MSG_SIZ];
9558
9559     if (strcmp(filename, "-") == 0) {
9560         return LoadPosition(stdin, n, "stdin");
9561     } else {
9562         f = fopen(filename, "rb");
9563         if (f == NULL) {
9564             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9565             DisplayError(buf, errno);
9566             return FALSE;
9567         } else {
9568             return LoadPosition(f, n, title);
9569         }
9570     }
9571 }
9572
9573 /* Load the nth position from the given open file, and close it */
9574 int
9575 LoadPosition(f, positionNumber, title)
9576      FILE *f;
9577      int positionNumber;
9578      char *title;
9579 {
9580     char *p, line[MSG_SIZ];
9581     Board initial_position;
9582     int i, j, fenMode, pn;
9583     
9584     if (gameMode == Training )
9585         SetTrainingModeOff();
9586
9587     if (gameMode != BeginningOfGame) {
9588         Reset(FALSE, TRUE);
9589     }
9590     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9591         fclose(lastLoadPositionFP);
9592     }
9593     if (positionNumber == 0) positionNumber = 1;
9594     lastLoadPositionFP = f;
9595     lastLoadPositionNumber = positionNumber;
9596     strcpy(lastLoadPositionTitle, title);
9597     if (first.pr == NoProc) {
9598       StartChessProgram(&first);
9599       InitChessProgram(&first, FALSE);
9600     }    
9601     pn = positionNumber;
9602     if (positionNumber < 0) {
9603         /* Negative position number means to seek to that byte offset */
9604         if (fseek(f, -positionNumber, 0) == -1) {
9605             DisplayError(_("Can't seek on position file"), 0);
9606             return FALSE;
9607         };
9608         pn = 1;
9609     } else {
9610         if (fseek(f, 0, 0) == -1) {
9611             if (f == lastLoadPositionFP ?
9612                 positionNumber == lastLoadPositionNumber + 1 :
9613                 positionNumber == 1) {
9614                 pn = 1;
9615             } else {
9616                 DisplayError(_("Can't seek on position file"), 0);
9617                 return FALSE;
9618             }
9619         }
9620     }
9621     /* See if this file is FEN or old-style xboard */
9622     if (fgets(line, MSG_SIZ, f) == NULL) {
9623         DisplayError(_("Position not found in file"), 0);
9624         return FALSE;
9625     }
9626     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9627     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9628
9629     if (pn >= 2) {
9630         if (fenMode || line[0] == '#') pn--;
9631         while (pn > 0) {
9632             /* skip positions before number pn */
9633             if (fgets(line, MSG_SIZ, f) == NULL) {
9634                 Reset(TRUE, TRUE);
9635                 DisplayError(_("Position not found in file"), 0);
9636                 return FALSE;
9637             }
9638             if (fenMode || line[0] == '#') pn--;
9639         }
9640     }
9641
9642     if (fenMode) {
9643         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9644             DisplayError(_("Bad FEN position in file"), 0);
9645             return FALSE;
9646         }
9647     } else {
9648         (void) fgets(line, MSG_SIZ, f);
9649         (void) fgets(line, MSG_SIZ, f);
9650     
9651         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9652             (void) fgets(line, MSG_SIZ, f);
9653             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9654                 if (*p == ' ')
9655                   continue;
9656                 initial_position[i][j++] = CharToPiece(*p);
9657             }
9658         }
9659     
9660         blackPlaysFirst = FALSE;
9661         if (!feof(f)) {
9662             (void) fgets(line, MSG_SIZ, f);
9663             if (strncmp(line, "black", strlen("black"))==0)
9664               blackPlaysFirst = TRUE;
9665         }
9666     }
9667     startedFromSetupPosition = TRUE;
9668     
9669     SendToProgram("force\n", &first);
9670     CopyBoard(boards[0], initial_position);
9671     if (blackPlaysFirst) {
9672         currentMove = forwardMostMove = backwardMostMove = 1;
9673         strcpy(moveList[0], "");
9674         strcpy(parseList[0], "");
9675         CopyBoard(boards[1], initial_position);
9676         DisplayMessage("", _("Black to play"));
9677     } else {
9678         currentMove = forwardMostMove = backwardMostMove = 0;
9679         DisplayMessage("", _("White to play"));
9680     }
9681           /* [HGM] copy FEN attributes as well */
9682           {   int i;
9683               initialRulePlies = FENrulePlies;
9684               epStatus[forwardMostMove] = FENepStatus;
9685               for( i=0; i< nrCastlingRights; i++ )
9686                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9687           }
9688     SendBoard(&first, forwardMostMove);
9689     if (appData.debugMode) {
9690 int i, j;
9691   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9692   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9693         fprintf(debugFP, "Load Position\n");
9694     }
9695
9696     if (positionNumber > 1) {
9697         sprintf(line, "%s %d", title, positionNumber);
9698         DisplayTitle(line);
9699     } else {
9700         DisplayTitle(title);
9701     }
9702     gameMode = EditGame;
9703     ModeHighlight();
9704     ResetClocks();
9705     timeRemaining[0][1] = whiteTimeRemaining;
9706     timeRemaining[1][1] = blackTimeRemaining;
9707     DrawPosition(FALSE, boards[currentMove]);
9708    
9709     return TRUE;
9710 }
9711
9712
9713 void
9714 CopyPlayerNameIntoFileName(dest, src)
9715      char **dest, *src;
9716 {
9717     while (*src != NULLCHAR && *src != ',') {
9718         if (*src == ' ') {
9719             *(*dest)++ = '_';
9720             src++;
9721         } else {
9722             *(*dest)++ = *src++;
9723         }
9724     }
9725 }
9726
9727 char *DefaultFileName(ext)
9728      char *ext;
9729 {
9730     static char def[MSG_SIZ];
9731     char *p;
9732
9733     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9734         p = def;
9735         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9736         *p++ = '-';
9737         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9738         *p++ = '.';
9739         strcpy(p, ext);
9740     } else {
9741         def[0] = NULLCHAR;
9742     }
9743     return def;
9744 }
9745
9746 /* Save the current game to the given file */
9747 int
9748 SaveGameToFile(filename, append)
9749      char *filename;
9750      int append;
9751 {
9752     FILE *f;
9753     char buf[MSG_SIZ];
9754
9755     if (strcmp(filename, "-") == 0) {
9756         return SaveGame(stdout, 0, NULL);
9757     } else {
9758         f = fopen(filename, append ? "a" : "w");
9759         if (f == NULL) {
9760             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9761             DisplayError(buf, errno);
9762             return FALSE;
9763         } else {
9764             return SaveGame(f, 0, NULL);
9765         }
9766     }
9767 }
9768
9769 char *
9770 SavePart(str)
9771      char *str;
9772 {
9773     static char buf[MSG_SIZ];
9774     char *p;
9775     
9776     p = strchr(str, ' ');
9777     if (p == NULL) return str;
9778     strncpy(buf, str, p - str);
9779     buf[p - str] = NULLCHAR;
9780     return buf;
9781 }
9782
9783 #define PGN_MAX_LINE 75
9784
9785 #define PGN_SIDE_WHITE  0
9786 #define PGN_SIDE_BLACK  1
9787
9788 /* [AS] */
9789 static int FindFirstMoveOutOfBook( int side )
9790 {
9791     int result = -1;
9792
9793     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9794         int index = backwardMostMove;
9795         int has_book_hit = 0;
9796
9797         if( (index % 2) != side ) {
9798             index++;
9799         }
9800
9801         while( index < forwardMostMove ) {
9802             /* Check to see if engine is in book */
9803             int depth = pvInfoList[index].depth;
9804             int score = pvInfoList[index].score;
9805             int in_book = 0;
9806
9807             if( depth <= 2 ) {
9808                 in_book = 1;
9809             }
9810             else if( score == 0 && depth == 63 ) {
9811                 in_book = 1; /* Zappa */
9812             }
9813             else if( score == 2 && depth == 99 ) {
9814                 in_book = 1; /* Abrok */
9815             }
9816
9817             has_book_hit += in_book;
9818
9819             if( ! in_book ) {
9820                 result = index;
9821
9822                 break;
9823             }
9824
9825             index += 2;
9826         }
9827     }
9828
9829     return result;
9830 }
9831
9832 /* [AS] */
9833 void GetOutOfBookInfo( char * buf )
9834 {
9835     int oob[2];
9836     int i;
9837     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9838
9839     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9840     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9841
9842     *buf = '\0';
9843
9844     if( oob[0] >= 0 || oob[1] >= 0 ) {
9845         for( i=0; i<2; i++ ) {
9846             int idx = oob[i];
9847
9848             if( idx >= 0 ) {
9849                 if( i > 0 && oob[0] >= 0 ) {
9850                     strcat( buf, "   " );
9851                 }
9852
9853                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9854                 sprintf( buf+strlen(buf), "%s%.2f", 
9855                     pvInfoList[idx].score >= 0 ? "+" : "",
9856                     pvInfoList[idx].score / 100.0 );
9857             }
9858         }
9859     }
9860 }
9861
9862 /* Save game in PGN style and close the file */
9863 int
9864 SaveGamePGN(f)
9865      FILE *f;
9866 {
9867     int i, offset, linelen, newblock;
9868     time_t tm;
9869 //    char *movetext;
9870     char numtext[32];
9871     int movelen, numlen, blank;
9872     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9873
9874     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9875     
9876     tm = time((time_t *) NULL);
9877     
9878     PrintPGNTags(f, &gameInfo);
9879     
9880     if (backwardMostMove > 0 || startedFromSetupPosition) {
9881         char *fen = PositionToFEN(backwardMostMove, NULL);
9882         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9883         fprintf(f, "\n{--------------\n");
9884         PrintPosition(f, backwardMostMove);
9885         fprintf(f, "--------------}\n");
9886         free(fen);
9887     }
9888     else {
9889         /* [AS] Out of book annotation */
9890         if( appData.saveOutOfBookInfo ) {
9891             char buf[64];
9892
9893             GetOutOfBookInfo( buf );
9894
9895             if( buf[0] != '\0' ) {
9896                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9897             }
9898         }
9899
9900         fprintf(f, "\n");
9901     }
9902
9903     i = backwardMostMove;
9904     linelen = 0;
9905     newblock = TRUE;
9906
9907     while (i < forwardMostMove) {
9908         /* Print comments preceding this move */
9909         if (commentList[i] != NULL) {
9910             if (linelen > 0) fprintf(f, "\n");
9911             fprintf(f, "{\n%s}\n", commentList[i]);
9912             linelen = 0;
9913             newblock = TRUE;
9914         }
9915
9916         /* Format move number */
9917         if ((i % 2) == 0) {
9918             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9919         } else {
9920             if (newblock) {
9921                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9922             } else {
9923                 numtext[0] = NULLCHAR;
9924             }
9925         }
9926         numlen = strlen(numtext);
9927         newblock = FALSE;
9928
9929         /* Print move number */
9930         blank = linelen > 0 && numlen > 0;
9931         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9932             fprintf(f, "\n");
9933             linelen = 0;
9934             blank = 0;
9935         }
9936         if (blank) {
9937             fprintf(f, " ");
9938             linelen++;
9939         }
9940         fprintf(f, "%s", numtext);
9941         linelen += numlen;
9942
9943         /* Get move */
9944         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9945         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9946
9947         /* Print move */
9948         blank = linelen > 0 && movelen > 0;
9949         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9950             fprintf(f, "\n");
9951             linelen = 0;
9952             blank = 0;
9953         }
9954         if (blank) {
9955             fprintf(f, " ");
9956             linelen++;
9957         }
9958         fprintf(f, "%s", move_buffer);
9959         linelen += movelen;
9960
9961         /* [AS] Add PV info if present */
9962         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9963             /* [HGM] add time */
9964             char buf[MSG_SIZ]; int seconds;
9965
9966             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9967
9968             if( seconds <= 0) buf[0] = 0; else
9969             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9970                 seconds = (seconds + 4)/10; // round to full seconds
9971                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9972                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9973             }
9974
9975             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9976                 pvInfoList[i].score >= 0 ? "+" : "",
9977                 pvInfoList[i].score / 100.0,
9978                 pvInfoList[i].depth,
9979                 buf );
9980
9981             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9982
9983             /* Print score/depth */
9984             blank = linelen > 0 && movelen > 0;
9985             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9986                 fprintf(f, "\n");
9987                 linelen = 0;
9988                 blank = 0;
9989             }
9990             if (blank) {
9991                 fprintf(f, " ");
9992                 linelen++;
9993             }
9994             fprintf(f, "%s", move_buffer);
9995             linelen += movelen;
9996         }
9997
9998         i++;
9999     }
10000     
10001     /* Start a new line */
10002     if (linelen > 0) fprintf(f, "\n");
10003
10004     /* Print comments after last move */
10005     if (commentList[i] != NULL) {
10006         fprintf(f, "{\n%s}\n", commentList[i]);
10007     }
10008
10009     /* Print result */
10010     if (gameInfo.resultDetails != NULL &&
10011         gameInfo.resultDetails[0] != NULLCHAR) {
10012         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10013                 PGNResult(gameInfo.result));
10014     } else {
10015         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10016     }
10017
10018     fclose(f);
10019     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10020     return TRUE;
10021 }
10022
10023 /* Save game in old style and close the file */
10024 int
10025 SaveGameOldStyle(f)
10026      FILE *f;
10027 {
10028     int i, offset;
10029     time_t tm;
10030     
10031     tm = time((time_t *) NULL);
10032     
10033     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10034     PrintOpponents(f);
10035     
10036     if (backwardMostMove > 0 || startedFromSetupPosition) {
10037         fprintf(f, "\n[--------------\n");
10038         PrintPosition(f, backwardMostMove);
10039         fprintf(f, "--------------]\n");
10040     } else {
10041         fprintf(f, "\n");
10042     }
10043
10044     i = backwardMostMove;
10045     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10046
10047     while (i < forwardMostMove) {
10048         if (commentList[i] != NULL) {
10049             fprintf(f, "[%s]\n", commentList[i]);
10050         }
10051
10052         if ((i % 2) == 1) {
10053             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10054             i++;
10055         } else {
10056             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10057             i++;
10058             if (commentList[i] != NULL) {
10059                 fprintf(f, "\n");
10060                 continue;
10061             }
10062             if (i >= forwardMostMove) {
10063                 fprintf(f, "\n");
10064                 break;
10065             }
10066             fprintf(f, "%s\n", parseList[i]);
10067             i++;
10068         }
10069     }
10070     
10071     if (commentList[i] != NULL) {
10072         fprintf(f, "[%s]\n", commentList[i]);
10073     }
10074
10075     /* This isn't really the old style, but it's close enough */
10076     if (gameInfo.resultDetails != NULL &&
10077         gameInfo.resultDetails[0] != NULLCHAR) {
10078         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10079                 gameInfo.resultDetails);
10080     } else {
10081         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10082     }
10083
10084     fclose(f);
10085     return TRUE;
10086 }
10087
10088 /* Save the current game to open file f and close the file */
10089 int
10090 SaveGame(f, dummy, dummy2)
10091      FILE *f;
10092      int dummy;
10093      char *dummy2;
10094 {
10095     if (gameMode == EditPosition) EditPositionDone(TRUE);
10096     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10097     if (appData.oldSaveStyle)
10098       return SaveGameOldStyle(f);
10099     else
10100       return SaveGamePGN(f);
10101 }
10102
10103 /* Save the current position to the given file */
10104 int
10105 SavePositionToFile(filename)
10106      char *filename;
10107 {
10108     FILE *f;
10109     char buf[MSG_SIZ];
10110
10111     if (strcmp(filename, "-") == 0) {
10112         return SavePosition(stdout, 0, NULL);
10113     } else {
10114         f = fopen(filename, "a");
10115         if (f == NULL) {
10116             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10117             DisplayError(buf, errno);
10118             return FALSE;
10119         } else {
10120             SavePosition(f, 0, NULL);
10121             return TRUE;
10122         }
10123     }
10124 }
10125
10126 /* Save the current position to the given open file and close the file */
10127 int
10128 SavePosition(f, dummy, dummy2)
10129      FILE *f;
10130      int dummy;
10131      char *dummy2;
10132 {
10133     time_t tm;
10134     char *fen;
10135
10136     if (gameMode == EditPosition) EditPositionDone(TRUE);
10137     if (appData.oldSaveStyle) {
10138         tm = time((time_t *) NULL);
10139     
10140         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10141         PrintOpponents(f);
10142         fprintf(f, "[--------------\n");
10143         PrintPosition(f, currentMove);
10144         fprintf(f, "--------------]\n");
10145     } else {
10146         fen = PositionToFEN(currentMove, NULL);
10147         fprintf(f, "%s\n", fen);
10148         free(fen);
10149     }
10150     fclose(f);
10151     return TRUE;
10152 }
10153
10154 void
10155 ReloadCmailMsgEvent(unregister)
10156      int unregister;
10157 {
10158 #if !WIN32
10159     static char *inFilename = NULL;
10160     static char *outFilename;
10161     int i;
10162     struct stat inbuf, outbuf;
10163     int status;
10164     
10165     /* Any registered moves are unregistered if unregister is set, */
10166     /* i.e. invoked by the signal handler */
10167     if (unregister) {
10168         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10169             cmailMoveRegistered[i] = FALSE;
10170             if (cmailCommentList[i] != NULL) {
10171                 free(cmailCommentList[i]);
10172                 cmailCommentList[i] = NULL;
10173             }
10174         }
10175         nCmailMovesRegistered = 0;
10176     }
10177
10178     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10179         cmailResult[i] = CMAIL_NOT_RESULT;
10180     }
10181     nCmailResults = 0;
10182
10183     if (inFilename == NULL) {
10184         /* Because the filenames are static they only get malloced once  */
10185         /* and they never get freed                                      */
10186         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10187         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10188
10189         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10190         sprintf(outFilename, "%s.out", appData.cmailGameName);
10191     }
10192     
10193     status = stat(outFilename, &outbuf);
10194     if (status < 0) {
10195         cmailMailedMove = FALSE;
10196     } else {
10197         status = stat(inFilename, &inbuf);
10198         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10199     }
10200     
10201     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10202        counts the games, notes how each one terminated, etc.
10203        
10204        It would be nice to remove this kludge and instead gather all
10205        the information while building the game list.  (And to keep it
10206        in the game list nodes instead of having a bunch of fixed-size
10207        parallel arrays.)  Note this will require getting each game's
10208        termination from the PGN tags, as the game list builder does
10209        not process the game moves.  --mann
10210        */
10211     cmailMsgLoaded = TRUE;
10212     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10213     
10214     /* Load first game in the file or popup game menu */
10215     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10216
10217 #endif /* !WIN32 */
10218     return;
10219 }
10220
10221 int
10222 RegisterMove()
10223 {
10224     FILE *f;
10225     char string[MSG_SIZ];
10226
10227     if (   cmailMailedMove
10228         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10229         return TRUE;            /* Allow free viewing  */
10230     }
10231
10232     /* Unregister move to ensure that we don't leave RegisterMove        */
10233     /* with the move registered when the conditions for registering no   */
10234     /* longer hold                                                       */
10235     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10236         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10237         nCmailMovesRegistered --;
10238
10239         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10240           {
10241               free(cmailCommentList[lastLoadGameNumber - 1]);
10242               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10243           }
10244     }
10245
10246     if (cmailOldMove == -1) {
10247         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10248         return FALSE;
10249     }
10250
10251     if (currentMove > cmailOldMove + 1) {
10252         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10253         return FALSE;
10254     }
10255
10256     if (currentMove < cmailOldMove) {
10257         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10258         return FALSE;
10259     }
10260
10261     if (forwardMostMove > currentMove) {
10262         /* Silently truncate extra moves */
10263         TruncateGame();
10264     }
10265
10266     if (   (currentMove == cmailOldMove + 1)
10267         || (   (currentMove == cmailOldMove)
10268             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10269                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10270         if (gameInfo.result != GameUnfinished) {
10271             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10272         }
10273
10274         if (commentList[currentMove] != NULL) {
10275             cmailCommentList[lastLoadGameNumber - 1]
10276               = StrSave(commentList[currentMove]);
10277         }
10278         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10279
10280         if (appData.debugMode)
10281           fprintf(debugFP, "Saving %s for game %d\n",
10282                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10283
10284         sprintf(string,
10285                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10286         
10287         f = fopen(string, "w");
10288         if (appData.oldSaveStyle) {
10289             SaveGameOldStyle(f); /* also closes the file */
10290             
10291             sprintf(string, "%s.pos.out", appData.cmailGameName);
10292             f = fopen(string, "w");
10293             SavePosition(f, 0, NULL); /* also closes the file */
10294         } else {
10295             fprintf(f, "{--------------\n");
10296             PrintPosition(f, currentMove);
10297             fprintf(f, "--------------}\n\n");
10298             
10299             SaveGame(f, 0, NULL); /* also closes the file*/
10300         }
10301         
10302         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10303         nCmailMovesRegistered ++;
10304     } else if (nCmailGames == 1) {
10305         DisplayError(_("You have not made a move yet"), 0);
10306         return FALSE;
10307     }
10308
10309     return TRUE;
10310 }
10311
10312 void
10313 MailMoveEvent()
10314 {
10315 #if !WIN32
10316     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10317     FILE *commandOutput;
10318     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10319     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10320     int nBuffers;
10321     int i;
10322     int archived;
10323     char *arcDir;
10324
10325     if (! cmailMsgLoaded) {
10326         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10327         return;
10328     }
10329
10330     if (nCmailGames == nCmailResults) {
10331         DisplayError(_("No unfinished games"), 0);
10332         return;
10333     }
10334
10335 #if CMAIL_PROHIBIT_REMAIL
10336     if (cmailMailedMove) {
10337         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);
10338         DisplayError(msg, 0);
10339         return;
10340     }
10341 #endif
10342
10343     if (! (cmailMailedMove || RegisterMove())) return;
10344     
10345     if (   cmailMailedMove
10346         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10347         sprintf(string, partCommandString,
10348                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10349         commandOutput = popen(string, "r");
10350
10351         if (commandOutput == NULL) {
10352             DisplayError(_("Failed to invoke cmail"), 0);
10353         } else {
10354             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10355                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10356             }
10357             if (nBuffers > 1) {
10358                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10359                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10360                 nBytes = MSG_SIZ - 1;
10361             } else {
10362                 (void) memcpy(msg, buffer, nBytes);
10363             }
10364             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10365
10366             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10367                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10368
10369                 archived = TRUE;
10370                 for (i = 0; i < nCmailGames; i ++) {
10371                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10372                         archived = FALSE;
10373                     }
10374                 }
10375                 if (   archived
10376                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10377                         != NULL)) {
10378                     sprintf(buffer, "%s/%s.%s.archive",
10379                             arcDir,
10380                             appData.cmailGameName,
10381                             gameInfo.date);
10382                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10383                     cmailMsgLoaded = FALSE;
10384                 }
10385             }
10386
10387             DisplayInformation(msg);
10388             pclose(commandOutput);
10389         }
10390     } else {
10391         if ((*cmailMsg) != '\0') {
10392             DisplayInformation(cmailMsg);
10393         }
10394     }
10395
10396     return;
10397 #endif /* !WIN32 */
10398 }
10399
10400 char *
10401 CmailMsg()
10402 {
10403 #if WIN32
10404     return NULL;
10405 #else
10406     int  prependComma = 0;
10407     char number[5];
10408     char string[MSG_SIZ];       /* Space for game-list */
10409     int  i;
10410     
10411     if (!cmailMsgLoaded) return "";
10412
10413     if (cmailMailedMove) {
10414         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10415     } else {
10416         /* Create a list of games left */
10417         sprintf(string, "[");
10418         for (i = 0; i < nCmailGames; i ++) {
10419             if (! (   cmailMoveRegistered[i]
10420                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10421                 if (prependComma) {
10422                     sprintf(number, ",%d", i + 1);
10423                 } else {
10424                     sprintf(number, "%d", i + 1);
10425                     prependComma = 1;
10426                 }
10427                 
10428                 strcat(string, number);
10429             }
10430         }
10431         strcat(string, "]");
10432
10433         if (nCmailMovesRegistered + nCmailResults == 0) {
10434             switch (nCmailGames) {
10435               case 1:
10436                 sprintf(cmailMsg,
10437                         _("Still need to make move for game\n"));
10438                 break;
10439                 
10440               case 2:
10441                 sprintf(cmailMsg,
10442                         _("Still need to make moves for both games\n"));
10443                 break;
10444                 
10445               default:
10446                 sprintf(cmailMsg,
10447                         _("Still need to make moves for all %d games\n"),
10448                         nCmailGames);
10449                 break;
10450             }
10451         } else {
10452             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10453               case 1:
10454                 sprintf(cmailMsg,
10455                         _("Still need to make a move for game %s\n"),
10456                         string);
10457                 break;
10458                 
10459               case 0:
10460                 if (nCmailResults == nCmailGames) {
10461                     sprintf(cmailMsg, _("No unfinished games\n"));
10462                 } else {
10463                     sprintf(cmailMsg, _("Ready to send mail\n"));
10464                 }
10465                 break;
10466                 
10467               default:
10468                 sprintf(cmailMsg,
10469                         _("Still need to make moves for games %s\n"),
10470                         string);
10471             }
10472         }
10473     }
10474     return cmailMsg;
10475 #endif /* WIN32 */
10476 }
10477
10478 void
10479 ResetGameEvent()
10480 {
10481     if (gameMode == Training)
10482       SetTrainingModeOff();
10483
10484     Reset(TRUE, TRUE);
10485     cmailMsgLoaded = FALSE;
10486     if (appData.icsActive) {
10487       SendToICS(ics_prefix);
10488       SendToICS("refresh\n");
10489     }
10490 }
10491
10492 void
10493 ExitEvent(status)
10494      int status;
10495 {
10496     exiting++;
10497     if (exiting > 2) {
10498       /* Give up on clean exit */
10499       exit(status);
10500     }
10501     if (exiting > 1) {
10502       /* Keep trying for clean exit */
10503       return;
10504     }
10505
10506     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10507
10508     if (telnetISR != NULL) {
10509       RemoveInputSource(telnetISR);
10510     }
10511     if (icsPR != NoProc) {
10512       DestroyChildProcess(icsPR, TRUE);
10513     }
10514
10515     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10516     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10517
10518     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10519     /* make sure this other one finishes before killing it!                  */
10520     if(endingGame) { int count = 0;
10521         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10522         while(endingGame && count++ < 10) DoSleep(1);
10523         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10524     }
10525
10526     /* Kill off chess programs */
10527     if (first.pr != NoProc) {
10528         ExitAnalyzeMode();
10529         
10530         DoSleep( appData.delayBeforeQuit );
10531         SendToProgram("quit\n", &first);
10532         DoSleep( appData.delayAfterQuit );
10533         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10534     }
10535     if (second.pr != NoProc) {
10536         DoSleep( appData.delayBeforeQuit );
10537         SendToProgram("quit\n", &second);
10538         DoSleep( appData.delayAfterQuit );
10539         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10540     }
10541     if (first.isr != NULL) {
10542         RemoveInputSource(first.isr);
10543     }
10544     if (second.isr != NULL) {
10545         RemoveInputSource(second.isr);
10546     }
10547
10548     ShutDownFrontEnd();
10549     exit(status);
10550 }
10551
10552 void
10553 PauseEvent()
10554 {
10555     if (appData.debugMode)
10556         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10557     if (pausing) {
10558         pausing = FALSE;
10559         ModeHighlight();
10560         if (gameMode == MachinePlaysWhite ||
10561             gameMode == MachinePlaysBlack) {
10562             StartClocks();
10563         } else {
10564             DisplayBothClocks();
10565         }
10566         if (gameMode == PlayFromGameFile) {
10567             if (appData.timeDelay >= 0) 
10568                 AutoPlayGameLoop();
10569         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10570             Reset(FALSE, TRUE);
10571             SendToICS(ics_prefix);
10572             SendToICS("refresh\n");
10573         } else if (currentMove < forwardMostMove) {
10574             ForwardInner(forwardMostMove);
10575         }
10576         pauseExamInvalid = FALSE;
10577     } else {
10578         switch (gameMode) {
10579           default:
10580             return;
10581           case IcsExamining:
10582             pauseExamForwardMostMove = forwardMostMove;
10583             pauseExamInvalid = FALSE;
10584             /* fall through */
10585           case IcsObserving:
10586           case IcsPlayingWhite:
10587           case IcsPlayingBlack:
10588             pausing = TRUE;
10589             ModeHighlight();
10590             return;
10591           case PlayFromGameFile:
10592             (void) StopLoadGameTimer();
10593             pausing = TRUE;
10594             ModeHighlight();
10595             break;
10596           case BeginningOfGame:
10597             if (appData.icsActive) return;
10598             /* else fall through */
10599           case MachinePlaysWhite:
10600           case MachinePlaysBlack:
10601           case TwoMachinesPlay:
10602             if (forwardMostMove == 0)
10603               return;           /* don't pause if no one has moved */
10604             if ((gameMode == MachinePlaysWhite &&
10605                  !WhiteOnMove(forwardMostMove)) ||
10606                 (gameMode == MachinePlaysBlack &&
10607                  WhiteOnMove(forwardMostMove))) {
10608                 StopClocks();
10609             }
10610             pausing = TRUE;
10611             ModeHighlight();
10612             break;
10613         }
10614     }
10615 }
10616
10617 void
10618 EditCommentEvent()
10619 {
10620     char title[MSG_SIZ];
10621
10622     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10623         strcpy(title, _("Edit comment"));
10624     } else {
10625         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10626                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10627                 parseList[currentMove - 1]);
10628     }
10629
10630     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10631 }
10632
10633
10634 void
10635 EditTagsEvent()
10636 {
10637     char *tags = PGNTags(&gameInfo);
10638     EditTagsPopUp(tags);
10639     free(tags);
10640 }
10641
10642 void
10643 AnalyzeModeEvent()
10644 {
10645     if (appData.noChessProgram || gameMode == AnalyzeMode)
10646       return;
10647
10648     if (gameMode != AnalyzeFile) {
10649         if (!appData.icsEngineAnalyze) {
10650                EditGameEvent();
10651                if (gameMode != EditGame) return;
10652         }
10653         ResurrectChessProgram();
10654         SendToProgram("analyze\n", &first);
10655         first.analyzing = TRUE;
10656         /*first.maybeThinking = TRUE;*/
10657         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10658         EngineOutputPopUp();
10659     }
10660     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10661     pausing = FALSE;
10662     ModeHighlight();
10663     SetGameInfo();
10664
10665     StartAnalysisClock();
10666     GetTimeMark(&lastNodeCountTime);
10667     lastNodeCount = 0;
10668 }
10669
10670 void
10671 AnalyzeFileEvent()
10672 {
10673     if (appData.noChessProgram || gameMode == AnalyzeFile)
10674       return;
10675
10676     if (gameMode != AnalyzeMode) {
10677         EditGameEvent();
10678         if (gameMode != EditGame) return;
10679         ResurrectChessProgram();
10680         SendToProgram("analyze\n", &first);
10681         first.analyzing = TRUE;
10682         /*first.maybeThinking = TRUE;*/
10683         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10684         EngineOutputPopUp();
10685     }
10686     gameMode = AnalyzeFile;
10687     pausing = FALSE;
10688     ModeHighlight();
10689     SetGameInfo();
10690
10691     StartAnalysisClock();
10692     GetTimeMark(&lastNodeCountTime);
10693     lastNodeCount = 0;
10694 }
10695
10696 void
10697 MachineWhiteEvent()
10698 {
10699     char buf[MSG_SIZ];
10700     char *bookHit = NULL;
10701
10702     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10703       return;
10704
10705
10706     if (gameMode == PlayFromGameFile || 
10707         gameMode == TwoMachinesPlay  || 
10708         gameMode == Training         || 
10709         gameMode == AnalyzeMode      || 
10710         gameMode == EndOfGame)
10711         EditGameEvent();
10712
10713     if (gameMode == EditPosition) 
10714         EditPositionDone(TRUE);
10715
10716     if (!WhiteOnMove(currentMove)) {
10717         DisplayError(_("It is not White's turn"), 0);
10718         return;
10719     }
10720   
10721     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10722       ExitAnalyzeMode();
10723
10724     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10725         gameMode == AnalyzeFile)
10726         TruncateGame();
10727
10728     ResurrectChessProgram();    /* in case it isn't running */
10729     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10730         gameMode = MachinePlaysWhite;
10731         ResetClocks();
10732     } else
10733     gameMode = MachinePlaysWhite;
10734     pausing = FALSE;
10735     ModeHighlight();
10736     SetGameInfo();
10737     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10738     DisplayTitle(buf);
10739     if (first.sendName) {
10740       sprintf(buf, "name %s\n", gameInfo.black);
10741       SendToProgram(buf, &first);
10742     }
10743     if (first.sendTime) {
10744       if (first.useColors) {
10745         SendToProgram("black\n", &first); /*gnu kludge*/
10746       }
10747       SendTimeRemaining(&first, TRUE);
10748     }
10749     if (first.useColors) {
10750       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10751     }
10752     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10753     SetMachineThinkingEnables();
10754     first.maybeThinking = TRUE;
10755     StartClocks();
10756     firstMove = FALSE;
10757
10758     if (appData.autoFlipView && !flipView) {
10759       flipView = !flipView;
10760       DrawPosition(FALSE, NULL);
10761       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10762     }
10763
10764     if(bookHit) { // [HGM] book: simulate book reply
10765         static char bookMove[MSG_SIZ]; // a bit generous?
10766
10767         programStats.nodes = programStats.depth = programStats.time = 
10768         programStats.score = programStats.got_only_move = 0;
10769         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10770
10771         strcpy(bookMove, "move ");
10772         strcat(bookMove, bookHit);
10773         HandleMachineMove(bookMove, &first);
10774     }
10775 }
10776
10777 void
10778 MachineBlackEvent()
10779 {
10780     char buf[MSG_SIZ];
10781    char *bookHit = NULL;
10782
10783     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10784         return;
10785
10786
10787     if (gameMode == PlayFromGameFile || 
10788         gameMode == TwoMachinesPlay  || 
10789         gameMode == Training         || 
10790         gameMode == AnalyzeMode      || 
10791         gameMode == EndOfGame)
10792         EditGameEvent();
10793
10794     if (gameMode == EditPosition) 
10795         EditPositionDone(TRUE);
10796
10797     if (WhiteOnMove(currentMove)) {
10798         DisplayError(_("It is not Black's turn"), 0);
10799         return;
10800     }
10801     
10802     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10803       ExitAnalyzeMode();
10804
10805     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10806         gameMode == AnalyzeFile)
10807         TruncateGame();
10808
10809     ResurrectChessProgram();    /* in case it isn't running */
10810     gameMode = MachinePlaysBlack;
10811     pausing = FALSE;
10812     ModeHighlight();
10813     SetGameInfo();
10814     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10815     DisplayTitle(buf);
10816     if (first.sendName) {
10817       sprintf(buf, "name %s\n", gameInfo.white);
10818       SendToProgram(buf, &first);
10819     }
10820     if (first.sendTime) {
10821       if (first.useColors) {
10822         SendToProgram("white\n", &first); /*gnu kludge*/
10823       }
10824       SendTimeRemaining(&first, FALSE);
10825     }
10826     if (first.useColors) {
10827       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10828     }
10829     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10830     SetMachineThinkingEnables();
10831     first.maybeThinking = TRUE;
10832     StartClocks();
10833
10834     if (appData.autoFlipView && flipView) {
10835       flipView = !flipView;
10836       DrawPosition(FALSE, NULL);
10837       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10838     }
10839     if(bookHit) { // [HGM] book: simulate book reply
10840         static char bookMove[MSG_SIZ]; // a bit generous?
10841
10842         programStats.nodes = programStats.depth = programStats.time = 
10843         programStats.score = programStats.got_only_move = 0;
10844         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10845
10846         strcpy(bookMove, "move ");
10847         strcat(bookMove, bookHit);
10848         HandleMachineMove(bookMove, &first);
10849     }
10850 }
10851
10852
10853 void
10854 DisplayTwoMachinesTitle()
10855 {
10856     char buf[MSG_SIZ];
10857     if (appData.matchGames > 0) {
10858         if (first.twoMachinesColor[0] == 'w') {
10859             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10860                     gameInfo.white, gameInfo.black,
10861                     first.matchWins, second.matchWins,
10862                     matchGame - 1 - (first.matchWins + second.matchWins));
10863         } else {
10864             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10865                     gameInfo.white, gameInfo.black,
10866                     second.matchWins, first.matchWins,
10867                     matchGame - 1 - (first.matchWins + second.matchWins));
10868         }
10869     } else {
10870         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10871     }
10872     DisplayTitle(buf);
10873 }
10874
10875 void
10876 TwoMachinesEvent P((void))
10877 {
10878     int i;
10879     char buf[MSG_SIZ];
10880     ChessProgramState *onmove;
10881     char *bookHit = NULL;
10882     
10883     if (appData.noChessProgram) return;
10884
10885     switch (gameMode) {
10886       case TwoMachinesPlay:
10887         return;
10888       case MachinePlaysWhite:
10889       case MachinePlaysBlack:
10890         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10891             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10892             return;
10893         }
10894         /* fall through */
10895       case BeginningOfGame:
10896       case PlayFromGameFile:
10897       case EndOfGame:
10898         EditGameEvent();
10899         if (gameMode != EditGame) return;
10900         break;
10901       case EditPosition:
10902         EditPositionDone(TRUE);
10903         break;
10904       case AnalyzeMode:
10905       case AnalyzeFile:
10906         ExitAnalyzeMode();
10907         break;
10908       case EditGame:
10909       default:
10910         break;
10911     }
10912
10913     forwardMostMove = currentMove;
10914     ResurrectChessProgram();    /* in case first program isn't running */
10915
10916     if (second.pr == NULL) {
10917         StartChessProgram(&second);
10918         if (second.protocolVersion == 1) {
10919           TwoMachinesEventIfReady();
10920         } else {
10921           /* kludge: allow timeout for initial "feature" command */
10922           FreezeUI();
10923           DisplayMessage("", _("Starting second chess program"));
10924           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10925         }
10926         return;
10927     }
10928     DisplayMessage("", "");
10929     InitChessProgram(&second, FALSE);
10930     SendToProgram("force\n", &second);
10931     if (startedFromSetupPosition) {
10932         SendBoard(&second, backwardMostMove);
10933     if (appData.debugMode) {
10934         fprintf(debugFP, "Two Machines\n");
10935     }
10936     }
10937     for (i = backwardMostMove; i < forwardMostMove; i++) {
10938         SendMoveToProgram(i, &second);
10939     }
10940
10941     gameMode = TwoMachinesPlay;
10942     pausing = FALSE;
10943     ModeHighlight();
10944     SetGameInfo();
10945     DisplayTwoMachinesTitle();
10946     firstMove = TRUE;
10947     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10948         onmove = &first;
10949     } else {
10950         onmove = &second;
10951     }
10952
10953     SendToProgram(first.computerString, &first);
10954     if (first.sendName) {
10955       sprintf(buf, "name %s\n", second.tidy);
10956       SendToProgram(buf, &first);
10957     }
10958     SendToProgram(second.computerString, &second);
10959     if (second.sendName) {
10960       sprintf(buf, "name %s\n", first.tidy);
10961       SendToProgram(buf, &second);
10962     }
10963
10964     ResetClocks();
10965     if (!first.sendTime || !second.sendTime) {
10966         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10967         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10968     }
10969     if (onmove->sendTime) {
10970       if (onmove->useColors) {
10971         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10972       }
10973       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10974     }
10975     if (onmove->useColors) {
10976       SendToProgram(onmove->twoMachinesColor, onmove);
10977     }
10978     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10979 //    SendToProgram("go\n", onmove);
10980     onmove->maybeThinking = TRUE;
10981     SetMachineThinkingEnables();
10982
10983     StartClocks();
10984
10985     if(bookHit) { // [HGM] book: simulate book reply
10986         static char bookMove[MSG_SIZ]; // a bit generous?
10987
10988         programStats.nodes = programStats.depth = programStats.time = 
10989         programStats.score = programStats.got_only_move = 0;
10990         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10991
10992         strcpy(bookMove, "move ");
10993         strcat(bookMove, bookHit);
10994         savedMessage = bookMove; // args for deferred call
10995         savedState = onmove;
10996         ScheduleDelayedEvent(DeferredBookMove, 1);
10997     }
10998 }
10999
11000 void
11001 TrainingEvent()
11002 {
11003     if (gameMode == Training) {
11004       SetTrainingModeOff();
11005       gameMode = PlayFromGameFile;
11006       DisplayMessage("", _("Training mode off"));
11007     } else {
11008       gameMode = Training;
11009       animateTraining = appData.animate;
11010
11011       /* make sure we are not already at the end of the game */
11012       if (currentMove < forwardMostMove) {
11013         SetTrainingModeOn();
11014         DisplayMessage("", _("Training mode on"));
11015       } else {
11016         gameMode = PlayFromGameFile;
11017         DisplayError(_("Already at end of game"), 0);
11018       }
11019     }
11020     ModeHighlight();
11021 }
11022
11023 void
11024 IcsClientEvent()
11025 {
11026     if (!appData.icsActive) return;
11027     switch (gameMode) {
11028       case IcsPlayingWhite:
11029       case IcsPlayingBlack:
11030       case IcsObserving:
11031       case IcsIdle:
11032       case BeginningOfGame:
11033       case IcsExamining:
11034         return;
11035
11036       case EditGame:
11037         break;
11038
11039       case EditPosition:
11040         EditPositionDone(TRUE);
11041         break;
11042
11043       case AnalyzeMode:
11044       case AnalyzeFile:
11045         ExitAnalyzeMode();
11046         break;
11047         
11048       default:
11049         EditGameEvent();
11050         break;
11051     }
11052
11053     gameMode = IcsIdle;
11054     ModeHighlight();
11055     return;
11056 }
11057
11058
11059 void
11060 EditGameEvent()
11061 {
11062     int i;
11063
11064     switch (gameMode) {
11065       case Training:
11066         SetTrainingModeOff();
11067         break;
11068       case MachinePlaysWhite:
11069       case MachinePlaysBlack:
11070       case BeginningOfGame:
11071         SendToProgram("force\n", &first);
11072         SetUserThinkingEnables();
11073         break;
11074       case PlayFromGameFile:
11075         (void) StopLoadGameTimer();
11076         if (gameFileFP != NULL) {
11077             gameFileFP = NULL;
11078         }
11079         break;
11080       case EditPosition:
11081         EditPositionDone(TRUE);
11082         break;
11083       case AnalyzeMode:
11084       case AnalyzeFile:
11085         ExitAnalyzeMode();
11086         SendToProgram("force\n", &first);
11087         break;
11088       case TwoMachinesPlay:
11089         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11090         ResurrectChessProgram();
11091         SetUserThinkingEnables();
11092         break;
11093       case EndOfGame:
11094         ResurrectChessProgram();
11095         break;
11096       case IcsPlayingBlack:
11097       case IcsPlayingWhite:
11098         DisplayError(_("Warning: You are still playing a game"), 0);
11099         break;
11100       case IcsObserving:
11101         DisplayError(_("Warning: You are still observing a game"), 0);
11102         break;
11103       case IcsExamining:
11104         DisplayError(_("Warning: You are still examining a game"), 0);
11105         break;
11106       case IcsIdle:
11107         break;
11108       case EditGame:
11109       default:
11110         return;
11111     }
11112     
11113     pausing = FALSE;
11114     StopClocks();
11115     first.offeredDraw = second.offeredDraw = 0;
11116
11117     if (gameMode == PlayFromGameFile) {
11118         whiteTimeRemaining = timeRemaining[0][currentMove];
11119         blackTimeRemaining = timeRemaining[1][currentMove];
11120         DisplayTitle("");
11121     }
11122
11123     if (gameMode == MachinePlaysWhite ||
11124         gameMode == MachinePlaysBlack ||
11125         gameMode == TwoMachinesPlay ||
11126         gameMode == EndOfGame) {
11127         i = forwardMostMove;
11128         while (i > currentMove) {
11129             SendToProgram("undo\n", &first);
11130             i--;
11131         }
11132         whiteTimeRemaining = timeRemaining[0][currentMove];
11133         blackTimeRemaining = timeRemaining[1][currentMove];
11134         DisplayBothClocks();
11135         if (whiteFlag || blackFlag) {
11136             whiteFlag = blackFlag = 0;
11137         }
11138         DisplayTitle("");
11139     }           
11140     
11141     gameMode = EditGame;
11142     ModeHighlight();
11143     SetGameInfo();
11144 }
11145
11146
11147 void
11148 EditPositionEvent()
11149 {
11150     if (gameMode == EditPosition) {
11151         EditGameEvent();
11152         return;
11153     }
11154     
11155     EditGameEvent();
11156     if (gameMode != EditGame) return;
11157     
11158     gameMode = EditPosition;
11159     ModeHighlight();
11160     SetGameInfo();
11161     if (currentMove > 0)
11162       CopyBoard(boards[0], boards[currentMove]);
11163     
11164     blackPlaysFirst = !WhiteOnMove(currentMove);
11165     ResetClocks();
11166     currentMove = forwardMostMove = backwardMostMove = 0;
11167     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11168     DisplayMove(-1);
11169 }
11170
11171 void
11172 ExitAnalyzeMode()
11173 {
11174     /* [DM] icsEngineAnalyze - possible call from other functions */
11175     if (appData.icsEngineAnalyze) {
11176         appData.icsEngineAnalyze = FALSE;
11177
11178         DisplayMessage("",_("Close ICS engine analyze..."));
11179     }
11180     if (first.analysisSupport && first.analyzing) {
11181       SendToProgram("exit\n", &first);
11182       first.analyzing = FALSE;
11183     }
11184     thinkOutput[0] = NULLCHAR;
11185 }
11186
11187 void
11188 EditPositionDone(Boolean fakeRights)
11189 {
11190     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11191
11192     startedFromSetupPosition = TRUE;
11193     InitChessProgram(&first, FALSE);
11194     if(fakeRights)  
11195       { /* don't do this if we just pasted FEN */
11196         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11197         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11198           {
11199             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11200             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11201           } 
11202         else 
11203           castlingRights[0][2] = -1;
11204         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11205           {
11206             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11207             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11208           } 
11209         else 
11210           castlingRights[0][5] = -1;
11211       }
11212     SendToProgram("force\n", &first);
11213     if (blackPlaysFirst) {
11214         strcpy(moveList[0], "");
11215         strcpy(parseList[0], "");
11216         currentMove = forwardMostMove = backwardMostMove = 1;
11217         CopyBoard(boards[1], boards[0]);
11218         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11219         { int i;
11220           epStatus[1] = epStatus[0];
11221           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11222         }
11223     } else {
11224         currentMove = forwardMostMove = backwardMostMove = 0;
11225     }
11226     SendBoard(&first, forwardMostMove);
11227     if (appData.debugMode) {
11228         fprintf(debugFP, "EditPosDone\n");
11229     }
11230     DisplayTitle("");
11231     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11232     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11233     gameMode = EditGame;
11234     ModeHighlight();
11235     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11236     ClearHighlights(); /* [AS] */
11237 }
11238
11239 /* Pause for `ms' milliseconds */
11240 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11241 void
11242 TimeDelay(ms)
11243      long ms;
11244 {
11245     TimeMark m1, m2;
11246
11247     GetTimeMark(&m1);
11248     do {
11249         GetTimeMark(&m2);
11250     } while (SubtractTimeMarks(&m2, &m1) < ms);
11251 }
11252
11253 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11254 void
11255 SendMultiLineToICS(buf)
11256      char *buf;
11257 {
11258     char temp[MSG_SIZ+1], *p;
11259     int len;
11260
11261     len = strlen(buf);
11262     if (len > MSG_SIZ)
11263       len = MSG_SIZ;
11264   
11265     strncpy(temp, buf, len);
11266     temp[len] = 0;
11267
11268     p = temp;
11269     while (*p) {
11270         if (*p == '\n' || *p == '\r')
11271           *p = ' ';
11272         ++p;
11273     }
11274
11275     strcat(temp, "\n");
11276     SendToICS(temp);
11277     SendToPlayer(temp, strlen(temp));
11278 }
11279
11280 void
11281 SetWhiteToPlayEvent()
11282 {
11283     if (gameMode == EditPosition) {
11284         blackPlaysFirst = FALSE;
11285         DisplayBothClocks();    /* works because currentMove is 0 */
11286     } else if (gameMode == IcsExamining) {
11287         SendToICS(ics_prefix);
11288         SendToICS("tomove white\n");
11289     }
11290 }
11291
11292 void
11293 SetBlackToPlayEvent()
11294 {
11295     if (gameMode == EditPosition) {
11296         blackPlaysFirst = TRUE;
11297         currentMove = 1;        /* kludge */
11298         DisplayBothClocks();
11299         currentMove = 0;
11300     } else if (gameMode == IcsExamining) {
11301         SendToICS(ics_prefix);
11302         SendToICS("tomove black\n");
11303     }
11304 }
11305
11306 void
11307 EditPositionMenuEvent(selection, x, y)
11308      ChessSquare selection;
11309      int x, y;
11310 {
11311     char buf[MSG_SIZ];
11312     ChessSquare piece = boards[0][y][x];
11313
11314     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11315
11316     switch (selection) {
11317       case ClearBoard:
11318         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11319             SendToICS(ics_prefix);
11320             SendToICS("bsetup clear\n");
11321         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11322             SendToICS(ics_prefix);
11323             SendToICS("clearboard\n");
11324         } else {
11325             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11326                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11327                 for (y = 0; y < BOARD_HEIGHT; y++) {
11328                     if (gameMode == IcsExamining) {
11329                         if (boards[currentMove][y][x] != EmptySquare) {
11330                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11331                                     AAA + x, ONE + y);
11332                             SendToICS(buf);
11333                         }
11334                     } else {
11335                         boards[0][y][x] = p;
11336                     }
11337                 }
11338             }
11339         }
11340         if (gameMode == EditPosition) {
11341             DrawPosition(FALSE, boards[0]);
11342         }
11343         break;
11344
11345       case WhitePlay:
11346         SetWhiteToPlayEvent();
11347         break;
11348
11349       case BlackPlay:
11350         SetBlackToPlayEvent();
11351         break;
11352
11353       case EmptySquare:
11354         if (gameMode == IcsExamining) {
11355             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11356             SendToICS(buf);
11357         } else {
11358             boards[0][y][x] = EmptySquare;
11359             DrawPosition(FALSE, boards[0]);
11360         }
11361         break;
11362
11363       case PromotePiece:
11364         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11365            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11366             selection = (ChessSquare) (PROMOTED piece);
11367         } else if(piece == EmptySquare) selection = WhiteSilver;
11368         else selection = (ChessSquare)((int)piece - 1);
11369         goto defaultlabel;
11370
11371       case DemotePiece:
11372         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11373            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11374             selection = (ChessSquare) (DEMOTED piece);
11375         } else if(piece == EmptySquare) selection = BlackSilver;
11376         else selection = (ChessSquare)((int)piece + 1);       
11377         goto defaultlabel;
11378
11379       case WhiteQueen:
11380       case BlackQueen:
11381         if(gameInfo.variant == VariantShatranj ||
11382            gameInfo.variant == VariantXiangqi  ||
11383            gameInfo.variant == VariantCourier    )
11384             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11385         goto defaultlabel;
11386
11387       case WhiteKing:
11388       case BlackKing:
11389         if(gameInfo.variant == VariantXiangqi)
11390             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11391         if(gameInfo.variant == VariantKnightmate)
11392             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11393       default:
11394         defaultlabel:
11395         if (gameMode == IcsExamining) {
11396             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11397                     PieceToChar(selection), AAA + x, ONE + y);
11398             SendToICS(buf);
11399         } else {
11400             boards[0][y][x] = selection;
11401             DrawPosition(FALSE, boards[0]);
11402         }
11403         break;
11404     }
11405 }
11406
11407
11408 void
11409 DropMenuEvent(selection, x, y)
11410      ChessSquare selection;
11411      int x, y;
11412 {
11413     ChessMove moveType;
11414
11415     switch (gameMode) {
11416       case IcsPlayingWhite:
11417       case MachinePlaysBlack:
11418         if (!WhiteOnMove(currentMove)) {
11419             DisplayMoveError(_("It is Black's turn"));
11420             return;
11421         }
11422         moveType = WhiteDrop;
11423         break;
11424       case IcsPlayingBlack:
11425       case MachinePlaysWhite:
11426         if (WhiteOnMove(currentMove)) {
11427             DisplayMoveError(_("It is White's turn"));
11428             return;
11429         }
11430         moveType = BlackDrop;
11431         break;
11432       case EditGame:
11433         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11434         break;
11435       default:
11436         return;
11437     }
11438
11439     if (moveType == BlackDrop && selection < BlackPawn) {
11440       selection = (ChessSquare) ((int) selection
11441                                  + (int) BlackPawn - (int) WhitePawn);
11442     }
11443     if (boards[currentMove][y][x] != EmptySquare) {
11444         DisplayMoveError(_("That square is occupied"));
11445         return;
11446     }
11447
11448     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11449 }
11450
11451 void
11452 AcceptEvent()
11453 {
11454     /* Accept a pending offer of any kind from opponent */
11455     
11456     if (appData.icsActive) {
11457         SendToICS(ics_prefix);
11458         SendToICS("accept\n");
11459     } else if (cmailMsgLoaded) {
11460         if (currentMove == cmailOldMove &&
11461             commentList[cmailOldMove] != NULL &&
11462             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11463                    "Black offers a draw" : "White offers a draw")) {
11464             TruncateGame();
11465             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11466             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11467         } else {
11468             DisplayError(_("There is no pending offer on this move"), 0);
11469             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11470         }
11471     } else {
11472         /* Not used for offers from chess program */
11473     }
11474 }
11475
11476 void
11477 DeclineEvent()
11478 {
11479     /* Decline a pending offer of any kind from opponent */
11480     
11481     if (appData.icsActive) {
11482         SendToICS(ics_prefix);
11483         SendToICS("decline\n");
11484     } else if (cmailMsgLoaded) {
11485         if (currentMove == cmailOldMove &&
11486             commentList[cmailOldMove] != NULL &&
11487             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11488                    "Black offers a draw" : "White offers a draw")) {
11489 #ifdef NOTDEF
11490             AppendComment(cmailOldMove, "Draw declined");
11491             DisplayComment(cmailOldMove - 1, "Draw declined");
11492 #endif /*NOTDEF*/
11493         } else {
11494             DisplayError(_("There is no pending offer on this move"), 0);
11495         }
11496     } else {
11497         /* Not used for offers from chess program */
11498     }
11499 }
11500
11501 void
11502 RematchEvent()
11503 {
11504     /* Issue ICS rematch command */
11505     if (appData.icsActive) {
11506         SendToICS(ics_prefix);
11507         SendToICS("rematch\n");
11508     }
11509 }
11510
11511 void
11512 CallFlagEvent()
11513 {
11514     /* Call your opponent's flag (claim a win on time) */
11515     if (appData.icsActive) {
11516         SendToICS(ics_prefix);
11517         SendToICS("flag\n");
11518     } else {
11519         switch (gameMode) {
11520           default:
11521             return;
11522           case MachinePlaysWhite:
11523             if (whiteFlag) {
11524                 if (blackFlag)
11525                   GameEnds(GameIsDrawn, "Both players ran out of time",
11526                            GE_PLAYER);
11527                 else
11528                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11529             } else {
11530                 DisplayError(_("Your opponent is not out of time"), 0);
11531             }
11532             break;
11533           case MachinePlaysBlack:
11534             if (blackFlag) {
11535                 if (whiteFlag)
11536                   GameEnds(GameIsDrawn, "Both players ran out of time",
11537                            GE_PLAYER);
11538                 else
11539                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11540             } else {
11541                 DisplayError(_("Your opponent is not out of time"), 0);
11542             }
11543             break;
11544         }
11545     }
11546 }
11547
11548 void
11549 DrawEvent()
11550 {
11551     /* Offer draw or accept pending draw offer from opponent */
11552     
11553     if (appData.icsActive) {
11554         /* Note: tournament rules require draw offers to be
11555            made after you make your move but before you punch
11556            your clock.  Currently ICS doesn't let you do that;
11557            instead, you immediately punch your clock after making
11558            a move, but you can offer a draw at any time. */
11559         
11560         SendToICS(ics_prefix);
11561         SendToICS("draw\n");
11562     } else if (cmailMsgLoaded) {
11563         if (currentMove == cmailOldMove &&
11564             commentList[cmailOldMove] != NULL &&
11565             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11566                    "Black offers a draw" : "White offers a draw")) {
11567             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11568             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11569         } else if (currentMove == cmailOldMove + 1) {
11570             char *offer = WhiteOnMove(cmailOldMove) ?
11571               "White offers a draw" : "Black offers a draw";
11572             AppendComment(currentMove, offer);
11573             DisplayComment(currentMove - 1, offer);
11574             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11575         } else {
11576             DisplayError(_("You must make your move before offering a draw"), 0);
11577             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11578         }
11579     } else if (first.offeredDraw) {
11580         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11581     } else {
11582         if (first.sendDrawOffers) {
11583             SendToProgram("draw\n", &first);
11584             userOfferedDraw = TRUE;
11585         }
11586     }
11587 }
11588
11589 void
11590 AdjournEvent()
11591 {
11592     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11593     
11594     if (appData.icsActive) {
11595         SendToICS(ics_prefix);
11596         SendToICS("adjourn\n");
11597     } else {
11598         /* Currently GNU Chess doesn't offer or accept Adjourns */
11599     }
11600 }
11601
11602
11603 void
11604 AbortEvent()
11605 {
11606     /* Offer Abort or accept pending Abort offer from opponent */
11607     
11608     if (appData.icsActive) {
11609         SendToICS(ics_prefix);
11610         SendToICS("abort\n");
11611     } else {
11612         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11613     }
11614 }
11615
11616 void
11617 ResignEvent()
11618 {
11619     /* Resign.  You can do this even if it's not your turn. */
11620     
11621     if (appData.icsActive) {
11622         SendToICS(ics_prefix);
11623         SendToICS("resign\n");
11624     } else {
11625         switch (gameMode) {
11626           case MachinePlaysWhite:
11627             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11628             break;
11629           case MachinePlaysBlack:
11630             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11631             break;
11632           case EditGame:
11633             if (cmailMsgLoaded) {
11634                 TruncateGame();
11635                 if (WhiteOnMove(cmailOldMove)) {
11636                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11637                 } else {
11638                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11639                 }
11640                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11641             }
11642             break;
11643           default:
11644             break;
11645         }
11646     }
11647 }
11648
11649
11650 void
11651 StopObservingEvent()
11652 {
11653     /* Stop observing current games */
11654     SendToICS(ics_prefix);
11655     SendToICS("unobserve\n");
11656 }
11657
11658 void
11659 StopExaminingEvent()
11660 {
11661     /* Stop observing current game */
11662     SendToICS(ics_prefix);
11663     SendToICS("unexamine\n");
11664 }
11665
11666 void
11667 ForwardInner(target)
11668      int target;
11669 {
11670     int limit;
11671
11672     if (appData.debugMode)
11673         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11674                 target, currentMove, forwardMostMove);
11675
11676     if (gameMode == EditPosition)
11677       return;
11678
11679     if (gameMode == PlayFromGameFile && !pausing)
11680       PauseEvent();
11681     
11682     if (gameMode == IcsExamining && pausing)
11683       limit = pauseExamForwardMostMove;
11684     else
11685       limit = forwardMostMove;
11686     
11687     if (target > limit) target = limit;
11688
11689     if (target > 0 && moveList[target - 1][0]) {
11690         int fromX, fromY, toX, toY;
11691         toX = moveList[target - 1][2] - AAA;
11692         toY = moveList[target - 1][3] - ONE;
11693         if (moveList[target - 1][1] == '@') {
11694             if (appData.highlightLastMove) {
11695                 SetHighlights(-1, -1, toX, toY);
11696             }
11697         } else {
11698             fromX = moveList[target - 1][0] - AAA;
11699             fromY = moveList[target - 1][1] - ONE;
11700             if (target == currentMove + 1) {
11701                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11702             }
11703             if (appData.highlightLastMove) {
11704                 SetHighlights(fromX, fromY, toX, toY);
11705             }
11706         }
11707     }
11708     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11709         gameMode == Training || gameMode == PlayFromGameFile || 
11710         gameMode == AnalyzeFile) {
11711         while (currentMove < target) {
11712             SendMoveToProgram(currentMove++, &first);
11713         }
11714     } else {
11715         currentMove = target;
11716     }
11717     
11718     if (gameMode == EditGame || gameMode == EndOfGame) {
11719         whiteTimeRemaining = timeRemaining[0][currentMove];
11720         blackTimeRemaining = timeRemaining[1][currentMove];
11721     }
11722     DisplayBothClocks();
11723     DisplayMove(currentMove - 1);
11724     DrawPosition(FALSE, boards[currentMove]);
11725     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11726     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11727         DisplayComment(currentMove - 1, commentList[currentMove]);
11728     }
11729 }
11730
11731
11732 void
11733 ForwardEvent()
11734 {
11735     if (gameMode == IcsExamining && !pausing) {
11736         SendToICS(ics_prefix);
11737         SendToICS("forward\n");
11738     } else {
11739         ForwardInner(currentMove + 1);
11740     }
11741 }
11742
11743 void
11744 ToEndEvent()
11745 {
11746     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11747         /* to optimze, we temporarily turn off analysis mode while we feed
11748          * the remaining moves to the engine. Otherwise we get analysis output
11749          * after each move.
11750          */ 
11751         if (first.analysisSupport) {
11752           SendToProgram("exit\nforce\n", &first);
11753           first.analyzing = FALSE;
11754         }
11755     }
11756         
11757     if (gameMode == IcsExamining && !pausing) {
11758         SendToICS(ics_prefix);
11759         SendToICS("forward 999999\n");
11760     } else {
11761         ForwardInner(forwardMostMove);
11762     }
11763
11764     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11765         /* we have fed all the moves, so reactivate analysis mode */
11766         SendToProgram("analyze\n", &first);
11767         first.analyzing = TRUE;
11768         /*first.maybeThinking = TRUE;*/
11769         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11770     }
11771 }
11772
11773 void
11774 BackwardInner(target)
11775      int target;
11776 {
11777     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11778
11779     if (appData.debugMode)
11780         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11781                 target, currentMove, forwardMostMove);
11782
11783     if (gameMode == EditPosition) return;
11784     if (currentMove <= backwardMostMove) {
11785         ClearHighlights();
11786         DrawPosition(full_redraw, boards[currentMove]);
11787         return;
11788     }
11789     if (gameMode == PlayFromGameFile && !pausing)
11790       PauseEvent();
11791     
11792     if (moveList[target][0]) {
11793         int fromX, fromY, toX, toY;
11794         toX = moveList[target][2] - AAA;
11795         toY = moveList[target][3] - ONE;
11796         if (moveList[target][1] == '@') {
11797             if (appData.highlightLastMove) {
11798                 SetHighlights(-1, -1, toX, toY);
11799             }
11800         } else {
11801             fromX = moveList[target][0] - AAA;
11802             fromY = moveList[target][1] - ONE;
11803             if (target == currentMove - 1) {
11804                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11805             }
11806             if (appData.highlightLastMove) {
11807                 SetHighlights(fromX, fromY, toX, toY);
11808             }
11809         }
11810     }
11811     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11812         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11813         while (currentMove > target) {
11814             SendToProgram("undo\n", &first);
11815             currentMove--;
11816         }
11817     } else {
11818         currentMove = target;
11819     }
11820     
11821     if (gameMode == EditGame || gameMode == EndOfGame) {
11822         whiteTimeRemaining = timeRemaining[0][currentMove];
11823         blackTimeRemaining = timeRemaining[1][currentMove];
11824     }
11825     DisplayBothClocks();
11826     DisplayMove(currentMove - 1);
11827     DrawPosition(full_redraw, boards[currentMove]);
11828     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11829     // [HGM] PV info: routine tests if comment empty
11830     DisplayComment(currentMove - 1, commentList[currentMove]);
11831 }
11832
11833 void
11834 BackwardEvent()
11835 {
11836     if (gameMode == IcsExamining && !pausing) {
11837         SendToICS(ics_prefix);
11838         SendToICS("backward\n");
11839     } else {
11840         BackwardInner(currentMove - 1);
11841     }
11842 }
11843
11844 void
11845 ToStartEvent()
11846 {
11847     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11848         /* to optimize, we temporarily turn off analysis mode while we undo
11849          * all the moves. Otherwise we get analysis output after each undo.
11850          */ 
11851         if (first.analysisSupport) {
11852           SendToProgram("exit\nforce\n", &first);
11853           first.analyzing = FALSE;
11854         }
11855     }
11856
11857     if (gameMode == IcsExamining && !pausing) {
11858         SendToICS(ics_prefix);
11859         SendToICS("backward 999999\n");
11860     } else {
11861         BackwardInner(backwardMostMove);
11862     }
11863
11864     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11865         /* we have fed all the moves, so reactivate analysis mode */
11866         SendToProgram("analyze\n", &first);
11867         first.analyzing = TRUE;
11868         /*first.maybeThinking = TRUE;*/
11869         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11870     }
11871 }
11872
11873 void
11874 ToNrEvent(int to)
11875 {
11876   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11877   if (to >= forwardMostMove) to = forwardMostMove;
11878   if (to <= backwardMostMove) to = backwardMostMove;
11879   if (to < currentMove) {
11880     BackwardInner(to);
11881   } else {
11882     ForwardInner(to);
11883   }
11884 }
11885
11886 void
11887 RevertEvent()
11888 {
11889     if (gameMode != IcsExamining) {
11890         DisplayError(_("You are not examining a game"), 0);
11891         return;
11892     }
11893     if (pausing) {
11894         DisplayError(_("You can't revert while pausing"), 0);
11895         return;
11896     }
11897     SendToICS(ics_prefix);
11898     SendToICS("revert\n");
11899 }
11900
11901 void
11902 RetractMoveEvent()
11903 {
11904     switch (gameMode) {
11905       case MachinePlaysWhite:
11906       case MachinePlaysBlack:
11907         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11908             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11909             return;
11910         }
11911         if (forwardMostMove < 2) return;
11912         currentMove = forwardMostMove = forwardMostMove - 2;
11913         whiteTimeRemaining = timeRemaining[0][currentMove];
11914         blackTimeRemaining = timeRemaining[1][currentMove];
11915         DisplayBothClocks();
11916         DisplayMove(currentMove - 1);
11917         ClearHighlights();/*!! could figure this out*/
11918         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11919         SendToProgram("remove\n", &first);
11920         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11921         break;
11922
11923       case BeginningOfGame:
11924       default:
11925         break;
11926
11927       case IcsPlayingWhite:
11928       case IcsPlayingBlack:
11929         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11930             SendToICS(ics_prefix);
11931             SendToICS("takeback 2\n");
11932         } else {
11933             SendToICS(ics_prefix);
11934             SendToICS("takeback 1\n");
11935         }
11936         break;
11937     }
11938 }
11939
11940 void
11941 MoveNowEvent()
11942 {
11943     ChessProgramState *cps;
11944
11945     switch (gameMode) {
11946       case MachinePlaysWhite:
11947         if (!WhiteOnMove(forwardMostMove)) {
11948             DisplayError(_("It is your turn"), 0);
11949             return;
11950         }
11951         cps = &first;
11952         break;
11953       case MachinePlaysBlack:
11954         if (WhiteOnMove(forwardMostMove)) {
11955             DisplayError(_("It is your turn"), 0);
11956             return;
11957         }
11958         cps = &first;
11959         break;
11960       case TwoMachinesPlay:
11961         if (WhiteOnMove(forwardMostMove) ==
11962             (first.twoMachinesColor[0] == 'w')) {
11963             cps = &first;
11964         } else {
11965             cps = &second;
11966         }
11967         break;
11968       case BeginningOfGame:
11969       default:
11970         return;
11971     }
11972     SendToProgram("?\n", cps);
11973 }
11974
11975 void
11976 TruncateGameEvent()
11977 {
11978     EditGameEvent();
11979     if (gameMode != EditGame) return;
11980     TruncateGame();
11981 }
11982
11983 void
11984 TruncateGame()
11985 {
11986     if (forwardMostMove > currentMove) {
11987         if (gameInfo.resultDetails != NULL) {
11988             free(gameInfo.resultDetails);
11989             gameInfo.resultDetails = NULL;
11990             gameInfo.result = GameUnfinished;
11991         }
11992         forwardMostMove = currentMove;
11993         HistorySet(parseList, backwardMostMove, forwardMostMove,
11994                    currentMove-1);
11995     }
11996 }
11997
11998 void
11999 HintEvent()
12000 {
12001     if (appData.noChessProgram) return;
12002     switch (gameMode) {
12003       case MachinePlaysWhite:
12004         if (WhiteOnMove(forwardMostMove)) {
12005             DisplayError(_("Wait until your turn"), 0);
12006             return;
12007         }
12008         break;
12009       case BeginningOfGame:
12010       case MachinePlaysBlack:
12011         if (!WhiteOnMove(forwardMostMove)) {
12012             DisplayError(_("Wait until your turn"), 0);
12013             return;
12014         }
12015         break;
12016       default:
12017         DisplayError(_("No hint available"), 0);
12018         return;
12019     }
12020     SendToProgram("hint\n", &first);
12021     hintRequested = TRUE;
12022 }
12023
12024 void
12025 BookEvent()
12026 {
12027     if (appData.noChessProgram) return;
12028     switch (gameMode) {
12029       case MachinePlaysWhite:
12030         if (WhiteOnMove(forwardMostMove)) {
12031             DisplayError(_("Wait until your turn"), 0);
12032             return;
12033         }
12034         break;
12035       case BeginningOfGame:
12036       case MachinePlaysBlack:
12037         if (!WhiteOnMove(forwardMostMove)) {
12038             DisplayError(_("Wait until your turn"), 0);
12039             return;
12040         }
12041         break;
12042       case EditPosition:
12043         EditPositionDone(TRUE);
12044         break;
12045       case TwoMachinesPlay:
12046         return;
12047       default:
12048         break;
12049     }
12050     SendToProgram("bk\n", &first);
12051     bookOutput[0] = NULLCHAR;
12052     bookRequested = TRUE;
12053 }
12054
12055 void
12056 AboutGameEvent()
12057 {
12058     char *tags = PGNTags(&gameInfo);
12059     TagsPopUp(tags, CmailMsg());
12060     free(tags);
12061 }
12062
12063 /* end button procedures */
12064
12065 void
12066 PrintPosition(fp, move)
12067      FILE *fp;
12068      int move;
12069 {
12070     int i, j;
12071     
12072     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12073         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12074             char c = PieceToChar(boards[move][i][j]);
12075             fputc(c == 'x' ? '.' : c, fp);
12076             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12077         }
12078     }
12079     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12080       fprintf(fp, "white to play\n");
12081     else
12082       fprintf(fp, "black to play\n");
12083 }
12084
12085 void
12086 PrintOpponents(fp)
12087      FILE *fp;
12088 {
12089     if (gameInfo.white != NULL) {
12090         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12091     } else {
12092         fprintf(fp, "\n");
12093     }
12094 }
12095
12096 /* Find last component of program's own name, using some heuristics */
12097 void
12098 TidyProgramName(prog, host, buf)
12099      char *prog, *host, buf[MSG_SIZ];
12100 {
12101     char *p, *q;
12102     int local = (strcmp(host, "localhost") == 0);
12103     while (!local && (p = strchr(prog, ';')) != NULL) {
12104         p++;
12105         while (*p == ' ') p++;
12106         prog = p;
12107     }
12108     if (*prog == '"' || *prog == '\'') {
12109         q = strchr(prog + 1, *prog);
12110     } else {
12111         q = strchr(prog, ' ');
12112     }
12113     if (q == NULL) q = prog + strlen(prog);
12114     p = q;
12115     while (p >= prog && *p != '/' && *p != '\\') p--;
12116     p++;
12117     if(p == prog && *p == '"') p++;
12118     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12119     memcpy(buf, p, q - p);
12120     buf[q - p] = NULLCHAR;
12121     if (!local) {
12122         strcat(buf, "@");
12123         strcat(buf, host);
12124     }
12125 }
12126
12127 char *
12128 TimeControlTagValue()
12129 {
12130     char buf[MSG_SIZ];
12131     if (!appData.clockMode) {
12132         strcpy(buf, "-");
12133     } else if (movesPerSession > 0) {
12134         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12135     } else if (timeIncrement == 0) {
12136         sprintf(buf, "%ld", timeControl/1000);
12137     } else {
12138         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12139     }
12140     return StrSave(buf);
12141 }
12142
12143 void
12144 SetGameInfo()
12145 {
12146     /* This routine is used only for certain modes */
12147     VariantClass v = gameInfo.variant;
12148     ClearGameInfo(&gameInfo);
12149     gameInfo.variant = v;
12150
12151     switch (gameMode) {
12152       case MachinePlaysWhite:
12153         gameInfo.event = StrSave( appData.pgnEventHeader );
12154         gameInfo.site = StrSave(HostName());
12155         gameInfo.date = PGNDate();
12156         gameInfo.round = StrSave("-");
12157         gameInfo.white = StrSave(first.tidy);
12158         gameInfo.black = StrSave(UserName());
12159         gameInfo.timeControl = TimeControlTagValue();
12160         break;
12161
12162       case MachinePlaysBlack:
12163         gameInfo.event = StrSave( appData.pgnEventHeader );
12164         gameInfo.site = StrSave(HostName());
12165         gameInfo.date = PGNDate();
12166         gameInfo.round = StrSave("-");
12167         gameInfo.white = StrSave(UserName());
12168         gameInfo.black = StrSave(first.tidy);
12169         gameInfo.timeControl = TimeControlTagValue();
12170         break;
12171
12172       case TwoMachinesPlay:
12173         gameInfo.event = StrSave( appData.pgnEventHeader );
12174         gameInfo.site = StrSave(HostName());
12175         gameInfo.date = PGNDate();
12176         if (matchGame > 0) {
12177             char buf[MSG_SIZ];
12178             sprintf(buf, "%d", matchGame);
12179             gameInfo.round = StrSave(buf);
12180         } else {
12181             gameInfo.round = StrSave("-");
12182         }
12183         if (first.twoMachinesColor[0] == 'w') {
12184             gameInfo.white = StrSave(first.tidy);
12185             gameInfo.black = StrSave(second.tidy);
12186         } else {
12187             gameInfo.white = StrSave(second.tidy);
12188             gameInfo.black = StrSave(first.tidy);
12189         }
12190         gameInfo.timeControl = TimeControlTagValue();
12191         break;
12192
12193       case EditGame:
12194         gameInfo.event = StrSave("Edited game");
12195         gameInfo.site = StrSave(HostName());
12196         gameInfo.date = PGNDate();
12197         gameInfo.round = StrSave("-");
12198         gameInfo.white = StrSave("-");
12199         gameInfo.black = StrSave("-");
12200         break;
12201
12202       case EditPosition:
12203         gameInfo.event = StrSave("Edited position");
12204         gameInfo.site = StrSave(HostName());
12205         gameInfo.date = PGNDate();
12206         gameInfo.round = StrSave("-");
12207         gameInfo.white = StrSave("-");
12208         gameInfo.black = StrSave("-");
12209         break;
12210
12211       case IcsPlayingWhite:
12212       case IcsPlayingBlack:
12213       case IcsObserving:
12214       case IcsExamining:
12215         break;
12216
12217       case PlayFromGameFile:
12218         gameInfo.event = StrSave("Game from non-PGN file");
12219         gameInfo.site = StrSave(HostName());
12220         gameInfo.date = PGNDate();
12221         gameInfo.round = StrSave("-");
12222         gameInfo.white = StrSave("?");
12223         gameInfo.black = StrSave("?");
12224         break;
12225
12226       default:
12227         break;
12228     }
12229 }
12230
12231 void
12232 ReplaceComment(index, text)
12233      int index;
12234      char *text;
12235 {
12236     int len;
12237
12238     while (*text == '\n') text++;
12239     len = strlen(text);
12240     while (len > 0 && text[len - 1] == '\n') len--;
12241
12242     if (commentList[index] != NULL)
12243       free(commentList[index]);
12244
12245     if (len == 0) {
12246         commentList[index] = NULL;
12247         return;
12248     }
12249     commentList[index] = (char *) malloc(len + 2);
12250     strncpy(commentList[index], text, len);
12251     commentList[index][len] = '\n';
12252     commentList[index][len + 1] = NULLCHAR;
12253 }
12254
12255 void
12256 CrushCRs(text)
12257      char *text;
12258 {
12259   char *p = text;
12260   char *q = text;
12261   char ch;
12262
12263   do {
12264     ch = *p++;
12265     if (ch == '\r') continue;
12266     *q++ = ch;
12267   } while (ch != '\0');
12268 }
12269
12270 void
12271 AppendComment(index, text)
12272      int index;
12273      char *text;
12274 {
12275     int oldlen, len;
12276     char *old;
12277
12278     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12279
12280     CrushCRs(text);
12281     while (*text == '\n') text++;
12282     len = strlen(text);
12283     while (len > 0 && text[len - 1] == '\n') len--;
12284
12285     if (len == 0) return;
12286
12287     if (commentList[index] != NULL) {
12288         old = commentList[index];
12289         oldlen = strlen(old);
12290         commentList[index] = (char *) malloc(oldlen + len + 2);
12291         strcpy(commentList[index], old);
12292         free(old);
12293         strncpy(&commentList[index][oldlen], text, len);
12294         commentList[index][oldlen + len] = '\n';
12295         commentList[index][oldlen + len + 1] = NULLCHAR;
12296     } else {
12297         commentList[index] = (char *) malloc(len + 2);
12298         strncpy(commentList[index], text, len);
12299         commentList[index][len] = '\n';
12300         commentList[index][len + 1] = NULLCHAR;
12301     }
12302 }
12303
12304 static char * FindStr( char * text, char * sub_text )
12305 {
12306     char * result = strstr( text, sub_text );
12307
12308     if( result != NULL ) {
12309         result += strlen( sub_text );
12310     }
12311
12312     return result;
12313 }
12314
12315 /* [AS] Try to extract PV info from PGN comment */
12316 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12317 char *GetInfoFromComment( int index, char * text )
12318 {
12319     char * sep = text;
12320
12321     if( text != NULL && index > 0 ) {
12322         int score = 0;
12323         int depth = 0;
12324         int time = -1, sec = 0, deci;
12325         char * s_eval = FindStr( text, "[%eval " );
12326         char * s_emt = FindStr( text, "[%emt " );
12327
12328         if( s_eval != NULL || s_emt != NULL ) {
12329             /* New style */
12330             char delim;
12331
12332             if( s_eval != NULL ) {
12333                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12334                     return text;
12335                 }
12336
12337                 if( delim != ']' ) {
12338                     return text;
12339                 }
12340             }
12341
12342             if( s_emt != NULL ) {
12343             }
12344         }
12345         else {
12346             /* We expect something like: [+|-]nnn.nn/dd */
12347             int score_lo = 0;
12348
12349             sep = strchr( text, '/' );
12350             if( sep == NULL || sep < (text+4) ) {
12351                 return text;
12352             }
12353
12354             time = -1; sec = -1; deci = -1;
12355             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12356                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12357                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12358                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12359                 return text;
12360             }
12361
12362             if( score_lo < 0 || score_lo >= 100 ) {
12363                 return text;
12364             }
12365
12366             if(sec >= 0) time = 600*time + 10*sec; else
12367             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12368
12369             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12370
12371             /* [HGM] PV time: now locate end of PV info */
12372             while( *++sep >= '0' && *sep <= '9'); // strip depth
12373             if(time >= 0)
12374             while( *++sep >= '0' && *sep <= '9'); // strip time
12375             if(sec >= 0)
12376             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12377             if(deci >= 0)
12378             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12379             while(*sep == ' ') sep++;
12380         }
12381
12382         if( depth <= 0 ) {
12383             return text;
12384         }
12385
12386         if( time < 0 ) {
12387             time = -1;
12388         }
12389
12390         pvInfoList[index-1].depth = depth;
12391         pvInfoList[index-1].score = score;
12392         pvInfoList[index-1].time  = 10*time; // centi-sec
12393     }
12394     return sep;
12395 }
12396
12397 void
12398 SendToProgram(message, cps)
12399      char *message;
12400      ChessProgramState *cps;
12401 {
12402     int count, outCount, error;
12403     char buf[MSG_SIZ];
12404
12405     if (cps->pr == NULL) return;
12406     Attention(cps);
12407     
12408     if (appData.debugMode) {
12409         TimeMark now;
12410         GetTimeMark(&now);
12411         fprintf(debugFP, "%ld >%-6s: %s", 
12412                 SubtractTimeMarks(&now, &programStartTime),
12413                 cps->which, message);
12414     }
12415     
12416     count = strlen(message);
12417     outCount = OutputToProcess(cps->pr, message, count, &error);
12418     if (outCount < count && !exiting 
12419                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12420         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12421         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12422             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12423                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12424                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12425             } else {
12426                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12427             }
12428             gameInfo.resultDetails = StrSave(buf);
12429         }
12430         DisplayFatalError(buf, error, 1);
12431     }
12432 }
12433
12434 void
12435 ReceiveFromProgram(isr, closure, message, count, error)
12436      InputSourceRef isr;
12437      VOIDSTAR closure;
12438      char *message;
12439      int count;
12440      int error;
12441 {
12442     char *end_str;
12443     char buf[MSG_SIZ];
12444     ChessProgramState *cps = (ChessProgramState *)closure;
12445
12446     if (isr != cps->isr) return; /* Killed intentionally */
12447     if (count <= 0) {
12448         if (count == 0) {
12449             sprintf(buf,
12450                     _("Error: %s chess program (%s) exited unexpectedly"),
12451                     cps->which, cps->program);
12452         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12453                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12454                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12455                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12456                 } else {
12457                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12458                 }
12459                 gameInfo.resultDetails = StrSave(buf);
12460             }
12461             RemoveInputSource(cps->isr);
12462             DisplayFatalError(buf, 0, 1);
12463         } else {
12464             sprintf(buf,
12465                     _("Error reading from %s chess program (%s)"),
12466                     cps->which, cps->program);
12467             RemoveInputSource(cps->isr);
12468
12469             /* [AS] Program is misbehaving badly... kill it */
12470             if( count == -2 ) {
12471                 DestroyChildProcess( cps->pr, 9 );
12472                 cps->pr = NoProc;
12473             }
12474
12475             DisplayFatalError(buf, error, 1);
12476         }
12477         return;
12478     }
12479     
12480     if ((end_str = strchr(message, '\r')) != NULL)
12481       *end_str = NULLCHAR;
12482     if ((end_str = strchr(message, '\n')) != NULL)
12483       *end_str = NULLCHAR;
12484     
12485     if (appData.debugMode) {
12486         TimeMark now; int print = 1;
12487         char *quote = ""; char c; int i;
12488
12489         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12490                 char start = message[0];
12491                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12492                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12493                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12494                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12495                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12496                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12497                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12498                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12499                         { quote = "# "; print = (appData.engineComments == 2); }
12500                 message[0] = start; // restore original message
12501         }
12502         if(print) {
12503                 GetTimeMark(&now);
12504                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12505                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12506                         quote,
12507                         message);
12508         }
12509     }
12510
12511     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12512     if (appData.icsEngineAnalyze) {
12513         if (strstr(message, "whisper") != NULL ||
12514              strstr(message, "kibitz") != NULL || 
12515             strstr(message, "tellics") != NULL) return;
12516     }
12517
12518     HandleMachineMove(message, cps);
12519 }
12520
12521
12522 void
12523 SendTimeControl(cps, mps, tc, inc, sd, st)
12524      ChessProgramState *cps;
12525      int mps, inc, sd, st;
12526      long tc;
12527 {
12528     char buf[MSG_SIZ];
12529     int seconds;
12530
12531     if( timeControl_2 > 0 ) {
12532         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12533             tc = timeControl_2;
12534         }
12535     }
12536     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12537     inc /= cps->timeOdds;
12538     st  /= cps->timeOdds;
12539
12540     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12541
12542     if (st > 0) {
12543       /* Set exact time per move, normally using st command */
12544       if (cps->stKludge) {
12545         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12546         seconds = st % 60;
12547         if (seconds == 0) {
12548           sprintf(buf, "level 1 %d\n", st/60);
12549         } else {
12550           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12551         }
12552       } else {
12553         sprintf(buf, "st %d\n", st);
12554       }
12555     } else {
12556       /* Set conventional or incremental time control, using level command */
12557       if (seconds == 0) {
12558         /* Note old gnuchess bug -- minutes:seconds used to not work.
12559            Fixed in later versions, but still avoid :seconds
12560            when seconds is 0. */
12561         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12562       } else {
12563         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12564                 seconds, inc/1000);
12565       }
12566     }
12567     SendToProgram(buf, cps);
12568
12569     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12570     /* Orthogonally, limit search to given depth */
12571     if (sd > 0) {
12572       if (cps->sdKludge) {
12573         sprintf(buf, "depth\n%d\n", sd);
12574       } else {
12575         sprintf(buf, "sd %d\n", sd);
12576       }
12577       SendToProgram(buf, cps);
12578     }
12579
12580     if(cps->nps > 0) { /* [HGM] nps */
12581         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12582         else {
12583                 sprintf(buf, "nps %d\n", cps->nps);
12584               SendToProgram(buf, cps);
12585         }
12586     }
12587 }
12588
12589 ChessProgramState *WhitePlayer()
12590 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12591 {
12592     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12593        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12594         return &second;
12595     return &first;
12596 }
12597
12598 void
12599 SendTimeRemaining(cps, machineWhite)
12600      ChessProgramState *cps;
12601      int /*boolean*/ machineWhite;
12602 {
12603     char message[MSG_SIZ];
12604     long time, otime;
12605
12606     /* Note: this routine must be called when the clocks are stopped
12607        or when they have *just* been set or switched; otherwise
12608        it will be off by the time since the current tick started.
12609     */
12610     if (machineWhite) {
12611         time = whiteTimeRemaining / 10;
12612         otime = blackTimeRemaining / 10;
12613     } else {
12614         time = blackTimeRemaining / 10;
12615         otime = whiteTimeRemaining / 10;
12616     }
12617     /* [HGM] translate opponent's time by time-odds factor */
12618     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12619     if (appData.debugMode) {
12620         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12621     }
12622
12623     if (time <= 0) time = 1;
12624     if (otime <= 0) otime = 1;
12625     
12626     sprintf(message, "time %ld\n", time);
12627     SendToProgram(message, cps);
12628
12629     sprintf(message, "otim %ld\n", otime);
12630     SendToProgram(message, cps);
12631 }
12632
12633 int
12634 BoolFeature(p, name, loc, cps)
12635      char **p;
12636      char *name;
12637      int *loc;
12638      ChessProgramState *cps;
12639 {
12640   char buf[MSG_SIZ];
12641   int len = strlen(name);
12642   int val;
12643   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12644     (*p) += len + 1;
12645     sscanf(*p, "%d", &val);
12646     *loc = (val != 0);
12647     while (**p && **p != ' ') (*p)++;
12648     sprintf(buf, "accepted %s\n", name);
12649     SendToProgram(buf, cps);
12650     return TRUE;
12651   }
12652   return FALSE;
12653 }
12654
12655 int
12656 IntFeature(p, name, loc, cps)
12657      char **p;
12658      char *name;
12659      int *loc;
12660      ChessProgramState *cps;
12661 {
12662   char buf[MSG_SIZ];
12663   int len = strlen(name);
12664   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12665     (*p) += len + 1;
12666     sscanf(*p, "%d", loc);
12667     while (**p && **p != ' ') (*p)++;
12668     sprintf(buf, "accepted %s\n", name);
12669     SendToProgram(buf, cps);
12670     return TRUE;
12671   }
12672   return FALSE;
12673 }
12674
12675 int
12676 StringFeature(p, name, loc, cps)
12677      char **p;
12678      char *name;
12679      char loc[];
12680      ChessProgramState *cps;
12681 {
12682   char buf[MSG_SIZ];
12683   int len = strlen(name);
12684   if (strncmp((*p), name, len) == 0
12685       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12686     (*p) += len + 2;
12687     sscanf(*p, "%[^\"]", loc);
12688     while (**p && **p != '\"') (*p)++;
12689     if (**p == '\"') (*p)++;
12690     sprintf(buf, "accepted %s\n", name);
12691     SendToProgram(buf, cps);
12692     return TRUE;
12693   }
12694   return FALSE;
12695 }
12696
12697 int 
12698 ParseOption(Option *opt, ChessProgramState *cps)
12699 // [HGM] options: process the string that defines an engine option, and determine
12700 // name, type, default value, and allowed value range
12701 {
12702         char *p, *q, buf[MSG_SIZ];
12703         int n, min = (-1)<<31, max = 1<<31, def;
12704
12705         if(p = strstr(opt->name, " -spin ")) {
12706             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12707             if(max < min) max = min; // enforce consistency
12708             if(def < min) def = min;
12709             if(def > max) def = max;
12710             opt->value = def;
12711             opt->min = min;
12712             opt->max = max;
12713             opt->type = Spin;
12714         } else if((p = strstr(opt->name, " -slider "))) {
12715             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12716             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12717             if(max < min) max = min; // enforce consistency
12718             if(def < min) def = min;
12719             if(def > max) def = max;
12720             opt->value = def;
12721             opt->min = min;
12722             opt->max = max;
12723             opt->type = Spin; // Slider;
12724         } else if((p = strstr(opt->name, " -string "))) {
12725             opt->textValue = p+9;
12726             opt->type = TextBox;
12727         } else if((p = strstr(opt->name, " -file "))) {
12728             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12729             opt->textValue = p+7;
12730             opt->type = TextBox; // FileName;
12731         } else if((p = strstr(opt->name, " -path "))) {
12732             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12733             opt->textValue = p+7;
12734             opt->type = TextBox; // PathName;
12735         } else if(p = strstr(opt->name, " -check ")) {
12736             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12737             opt->value = (def != 0);
12738             opt->type = CheckBox;
12739         } else if(p = strstr(opt->name, " -combo ")) {
12740             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12741             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12742             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12743             opt->value = n = 0;
12744             while(q = StrStr(q, " /// ")) {
12745                 n++; *q = 0;    // count choices, and null-terminate each of them
12746                 q += 5;
12747                 if(*q == '*') { // remember default, which is marked with * prefix
12748                     q++;
12749                     opt->value = n;
12750                 }
12751                 cps->comboList[cps->comboCnt++] = q;
12752             }
12753             cps->comboList[cps->comboCnt++] = NULL;
12754             opt->max = n + 1;
12755             opt->type = ComboBox;
12756         } else if(p = strstr(opt->name, " -button")) {
12757             opt->type = Button;
12758         } else if(p = strstr(opt->name, " -save")) {
12759             opt->type = SaveButton;
12760         } else return FALSE;
12761         *p = 0; // terminate option name
12762         // now look if the command-line options define a setting for this engine option.
12763         if(cps->optionSettings && cps->optionSettings[0])
12764             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12765         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12766                 sprintf(buf, "option %s", p);
12767                 if(p = strstr(buf, ",")) *p = 0;
12768                 strcat(buf, "\n");
12769                 SendToProgram(buf, cps);
12770         }
12771         return TRUE;
12772 }
12773
12774 void
12775 FeatureDone(cps, val)
12776      ChessProgramState* cps;
12777      int val;
12778 {
12779   DelayedEventCallback cb = GetDelayedEvent();
12780   if ((cb == InitBackEnd3 && cps == &first) ||
12781       (cb == TwoMachinesEventIfReady && cps == &second)) {
12782     CancelDelayedEvent();
12783     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12784   }
12785   cps->initDone = val;
12786 }
12787
12788 /* Parse feature command from engine */
12789 void
12790 ParseFeatures(args, cps)
12791      char* args;
12792      ChessProgramState *cps;  
12793 {
12794   char *p = args;
12795   char *q;
12796   int val;
12797   char buf[MSG_SIZ];
12798
12799   for (;;) {
12800     while (*p == ' ') p++;
12801     if (*p == NULLCHAR) return;
12802
12803     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12804     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12805     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12806     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12807     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12808     if (BoolFeature(&p, "reuse", &val, cps)) {
12809       /* Engine can disable reuse, but can't enable it if user said no */
12810       if (!val) cps->reuse = FALSE;
12811       continue;
12812     }
12813     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12814     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12815       if (gameMode == TwoMachinesPlay) {
12816         DisplayTwoMachinesTitle();
12817       } else {
12818         DisplayTitle("");
12819       }
12820       continue;
12821     }
12822     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12823     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12824     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12825     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12826     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12827     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12828     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12829     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12830     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12831     if (IntFeature(&p, "done", &val, cps)) {
12832       FeatureDone(cps, val);
12833       continue;
12834     }
12835     /* Added by Tord: */
12836     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12837     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12838     /* End of additions by Tord */
12839
12840     /* [HGM] added features: */
12841     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12842     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12843     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12844     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12845     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12846     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12847     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12848         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12849             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12850             SendToProgram(buf, cps);
12851             continue;
12852         }
12853         if(cps->nrOptions >= MAX_OPTIONS) {
12854             cps->nrOptions--;
12855             sprintf(buf, "%s engine has too many options\n", cps->which);
12856             DisplayError(buf, 0);
12857         }
12858         continue;
12859     }
12860     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12861     /* End of additions by HGM */
12862
12863     /* unknown feature: complain and skip */
12864     q = p;
12865     while (*q && *q != '=') q++;
12866     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12867     SendToProgram(buf, cps);
12868     p = q;
12869     if (*p == '=') {
12870       p++;
12871       if (*p == '\"') {
12872         p++;
12873         while (*p && *p != '\"') p++;
12874         if (*p == '\"') p++;
12875       } else {
12876         while (*p && *p != ' ') p++;
12877       }
12878     }
12879   }
12880
12881 }
12882
12883 void
12884 PeriodicUpdatesEvent(newState)
12885      int newState;
12886 {
12887     if (newState == appData.periodicUpdates)
12888       return;
12889
12890     appData.periodicUpdates=newState;
12891
12892     /* Display type changes, so update it now */
12893 //    DisplayAnalysis();
12894
12895     /* Get the ball rolling again... */
12896     if (newState) {
12897         AnalysisPeriodicEvent(1);
12898         StartAnalysisClock();
12899     }
12900 }
12901
12902 void
12903 PonderNextMoveEvent(newState)
12904      int newState;
12905 {
12906     if (newState == appData.ponderNextMove) return;
12907     if (gameMode == EditPosition) EditPositionDone(TRUE);
12908     if (newState) {
12909         SendToProgram("hard\n", &first);
12910         if (gameMode == TwoMachinesPlay) {
12911             SendToProgram("hard\n", &second);
12912         }
12913     } else {
12914         SendToProgram("easy\n", &first);
12915         thinkOutput[0] = NULLCHAR;
12916         if (gameMode == TwoMachinesPlay) {
12917             SendToProgram("easy\n", &second);
12918         }
12919     }
12920     appData.ponderNextMove = newState;
12921 }
12922
12923 void
12924 NewSettingEvent(option, command, value)
12925      char *command;
12926      int option, value;
12927 {
12928     char buf[MSG_SIZ];
12929
12930     if (gameMode == EditPosition) EditPositionDone(TRUE);
12931     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12932     SendToProgram(buf, &first);
12933     if (gameMode == TwoMachinesPlay) {
12934         SendToProgram(buf, &second);
12935     }
12936 }
12937
12938 void
12939 ShowThinkingEvent()
12940 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12941 {
12942     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12943     int newState = appData.showThinking
12944         // [HGM] thinking: other features now need thinking output as well
12945         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12946     
12947     if (oldState == newState) return;
12948     oldState = newState;
12949     if (gameMode == EditPosition) EditPositionDone(TRUE);
12950     if (oldState) {
12951         SendToProgram("post\n", &first);
12952         if (gameMode == TwoMachinesPlay) {
12953             SendToProgram("post\n", &second);
12954         }
12955     } else {
12956         SendToProgram("nopost\n", &first);
12957         thinkOutput[0] = NULLCHAR;
12958         if (gameMode == TwoMachinesPlay) {
12959             SendToProgram("nopost\n", &second);
12960         }
12961     }
12962 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12963 }
12964
12965 void
12966 AskQuestionEvent(title, question, replyPrefix, which)
12967      char *title; char *question; char *replyPrefix; char *which;
12968 {
12969   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12970   if (pr == NoProc) return;
12971   AskQuestion(title, question, replyPrefix, pr);
12972 }
12973
12974 void
12975 DisplayMove(moveNumber)
12976      int moveNumber;
12977 {
12978     char message[MSG_SIZ];
12979     char res[MSG_SIZ];
12980     char cpThinkOutput[MSG_SIZ];
12981
12982     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12983     
12984     if (moveNumber == forwardMostMove - 1 || 
12985         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12986
12987         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12988
12989         if (strchr(cpThinkOutput, '\n')) {
12990             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12991         }
12992     } else {
12993         *cpThinkOutput = NULLCHAR;
12994     }
12995
12996     /* [AS] Hide thinking from human user */
12997     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12998         *cpThinkOutput = NULLCHAR;
12999         if( thinkOutput[0] != NULLCHAR ) {
13000             int i;
13001
13002             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13003                 cpThinkOutput[i] = '.';
13004             }
13005             cpThinkOutput[i] = NULLCHAR;
13006             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13007         }
13008     }
13009
13010     if (moveNumber == forwardMostMove - 1 &&
13011         gameInfo.resultDetails != NULL) {
13012         if (gameInfo.resultDetails[0] == NULLCHAR) {
13013             sprintf(res, " %s", PGNResult(gameInfo.result));
13014         } else {
13015             sprintf(res, " {%s} %s",
13016                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13017         }
13018     } else {
13019         res[0] = NULLCHAR;
13020     }
13021
13022     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13023         DisplayMessage(res, cpThinkOutput);
13024     } else {
13025         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13026                 WhiteOnMove(moveNumber) ? " " : ".. ",
13027                 parseList[moveNumber], res);
13028         DisplayMessage(message, cpThinkOutput);
13029     }
13030 }
13031
13032 void
13033 DisplayComment(moveNumber, text)
13034      int moveNumber;
13035      char *text;
13036 {
13037     char title[MSG_SIZ];
13038     char buf[8000]; // comment can be long!
13039     int score, depth;
13040     
13041     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13042       strcpy(title, "Comment");
13043     } else {
13044       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13045               WhiteOnMove(moveNumber) ? " " : ".. ",
13046               parseList[moveNumber]);
13047     }
13048     // [HGM] PV info: display PV info together with (or as) comment
13049     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13050       if(text == NULL) text = "";                                           
13051       score = pvInfoList[moveNumber].score;
13052       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13053               depth, (pvInfoList[moveNumber].time+50)/100, text);
13054       text = buf;
13055     }
13056     if (text != NULL && (appData.autoDisplayComment || commentUp))
13057         CommentPopUp(title, text);
13058 }
13059
13060 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13061  * might be busy thinking or pondering.  It can be omitted if your
13062  * gnuchess is configured to stop thinking immediately on any user
13063  * input.  However, that gnuchess feature depends on the FIONREAD
13064  * ioctl, which does not work properly on some flavors of Unix.
13065  */
13066 void
13067 Attention(cps)
13068      ChessProgramState *cps;
13069 {
13070 #if ATTENTION
13071     if (!cps->useSigint) return;
13072     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13073     switch (gameMode) {
13074       case MachinePlaysWhite:
13075       case MachinePlaysBlack:
13076       case TwoMachinesPlay:
13077       case IcsPlayingWhite:
13078       case IcsPlayingBlack:
13079       case AnalyzeMode:
13080       case AnalyzeFile:
13081         /* Skip if we know it isn't thinking */
13082         if (!cps->maybeThinking) return;
13083         if (appData.debugMode)
13084           fprintf(debugFP, "Interrupting %s\n", cps->which);
13085         InterruptChildProcess(cps->pr);
13086         cps->maybeThinking = FALSE;
13087         break;
13088       default:
13089         break;
13090     }
13091 #endif /*ATTENTION*/
13092 }
13093
13094 int
13095 CheckFlags()
13096 {
13097     if (whiteTimeRemaining <= 0) {
13098         if (!whiteFlag) {
13099             whiteFlag = TRUE;
13100             if (appData.icsActive) {
13101                 if (appData.autoCallFlag &&
13102                     gameMode == IcsPlayingBlack && !blackFlag) {
13103                   SendToICS(ics_prefix);
13104                   SendToICS("flag\n");
13105                 }
13106             } else {
13107                 if (blackFlag) {
13108                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13109                 } else {
13110                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13111                     if (appData.autoCallFlag) {
13112                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13113                         return TRUE;
13114                     }
13115                 }
13116             }
13117         }
13118     }
13119     if (blackTimeRemaining <= 0) {
13120         if (!blackFlag) {
13121             blackFlag = TRUE;
13122             if (appData.icsActive) {
13123                 if (appData.autoCallFlag &&
13124                     gameMode == IcsPlayingWhite && !whiteFlag) {
13125                   SendToICS(ics_prefix);
13126                   SendToICS("flag\n");
13127                 }
13128             } else {
13129                 if (whiteFlag) {
13130                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13131                 } else {
13132                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13133                     if (appData.autoCallFlag) {
13134                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13135                         return TRUE;
13136                     }
13137                 }
13138             }
13139         }
13140     }
13141     return FALSE;
13142 }
13143
13144 void
13145 CheckTimeControl()
13146 {
13147     if (!appData.clockMode || appData.icsActive ||
13148         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13149
13150     /*
13151      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13152      */
13153     if ( !WhiteOnMove(forwardMostMove) )
13154         /* White made time control */
13155         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13156         /* [HGM] time odds: correct new time quota for time odds! */
13157                                             / WhitePlayer()->timeOdds;
13158       else
13159         /* Black made time control */
13160         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13161                                             / WhitePlayer()->other->timeOdds;
13162 }
13163
13164 void
13165 DisplayBothClocks()
13166 {
13167     int wom = gameMode == EditPosition ?
13168       !blackPlaysFirst : WhiteOnMove(currentMove);
13169     DisplayWhiteClock(whiteTimeRemaining, wom);
13170     DisplayBlackClock(blackTimeRemaining, !wom);
13171 }
13172
13173
13174 /* Timekeeping seems to be a portability nightmare.  I think everyone
13175    has ftime(), but I'm really not sure, so I'm including some ifdefs
13176    to use other calls if you don't.  Clocks will be less accurate if
13177    you have neither ftime nor gettimeofday.
13178 */
13179
13180 /* VS 2008 requires the #include outside of the function */
13181 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13182 #include <sys/timeb.h>
13183 #endif
13184
13185 /* Get the current time as a TimeMark */
13186 void
13187 GetTimeMark(tm)
13188      TimeMark *tm;
13189 {
13190 #if HAVE_GETTIMEOFDAY
13191
13192     struct timeval timeVal;
13193     struct timezone timeZone;
13194
13195     gettimeofday(&timeVal, &timeZone);
13196     tm->sec = (long) timeVal.tv_sec; 
13197     tm->ms = (int) (timeVal.tv_usec / 1000L);
13198
13199 #else /*!HAVE_GETTIMEOFDAY*/
13200 #if HAVE_FTIME
13201
13202 // include <sys/timeb.h> / moved to just above start of function
13203     struct timeb timeB;
13204
13205     ftime(&timeB);
13206     tm->sec = (long) timeB.time;
13207     tm->ms = (int) timeB.millitm;
13208
13209 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13210     tm->sec = (long) time(NULL);
13211     tm->ms = 0;
13212 #endif
13213 #endif
13214 }
13215
13216 /* Return the difference in milliseconds between two
13217    time marks.  We assume the difference will fit in a long!
13218 */
13219 long
13220 SubtractTimeMarks(tm2, tm1)
13221      TimeMark *tm2, *tm1;
13222 {
13223     return 1000L*(tm2->sec - tm1->sec) +
13224            (long) (tm2->ms - tm1->ms);
13225 }
13226
13227
13228 /*
13229  * Code to manage the game clocks.
13230  *
13231  * In tournament play, black starts the clock and then white makes a move.
13232  * We give the human user a slight advantage if he is playing white---the
13233  * clocks don't run until he makes his first move, so it takes zero time.
13234  * Also, we don't account for network lag, so we could get out of sync
13235  * with GNU Chess's clock -- but then, referees are always right.  
13236  */
13237
13238 static TimeMark tickStartTM;
13239 static long intendedTickLength;
13240
13241 long
13242 NextTickLength(timeRemaining)
13243      long timeRemaining;
13244 {
13245     long nominalTickLength, nextTickLength;
13246
13247     if (timeRemaining > 0L && timeRemaining <= 10000L)
13248       nominalTickLength = 100L;
13249     else
13250       nominalTickLength = 1000L;
13251     nextTickLength = timeRemaining % nominalTickLength;
13252     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13253
13254     return nextTickLength;
13255 }
13256
13257 /* Adjust clock one minute up or down */
13258 void
13259 AdjustClock(Boolean which, int dir)
13260 {
13261     if(which) blackTimeRemaining += 60000*dir;
13262     else      whiteTimeRemaining += 60000*dir;
13263     DisplayBothClocks();
13264 }
13265
13266 /* Stop clocks and reset to a fresh time control */
13267 void
13268 ResetClocks() 
13269 {
13270     (void) StopClockTimer();
13271     if (appData.icsActive) {
13272         whiteTimeRemaining = blackTimeRemaining = 0;
13273     } else { /* [HGM] correct new time quote for time odds */
13274         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13275         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13276     }
13277     if (whiteFlag || blackFlag) {
13278         DisplayTitle("");
13279         whiteFlag = blackFlag = FALSE;
13280     }
13281     DisplayBothClocks();
13282 }
13283
13284 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13285
13286 /* Decrement running clock by amount of time that has passed */
13287 void
13288 DecrementClocks()
13289 {
13290     long timeRemaining;
13291     long lastTickLength, fudge;
13292     TimeMark now;
13293
13294     if (!appData.clockMode) return;
13295     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13296         
13297     GetTimeMark(&now);
13298
13299     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13300
13301     /* Fudge if we woke up a little too soon */
13302     fudge = intendedTickLength - lastTickLength;
13303     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13304
13305     if (WhiteOnMove(forwardMostMove)) {
13306         if(whiteNPS >= 0) lastTickLength = 0;
13307         timeRemaining = whiteTimeRemaining -= lastTickLength;
13308         DisplayWhiteClock(whiteTimeRemaining - fudge,
13309                           WhiteOnMove(currentMove));
13310     } else {
13311         if(blackNPS >= 0) lastTickLength = 0;
13312         timeRemaining = blackTimeRemaining -= lastTickLength;
13313         DisplayBlackClock(blackTimeRemaining - fudge,
13314                           !WhiteOnMove(currentMove));
13315     }
13316
13317     if (CheckFlags()) return;
13318         
13319     tickStartTM = now;
13320     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13321     StartClockTimer(intendedTickLength);
13322
13323     /* if the time remaining has fallen below the alarm threshold, sound the
13324      * alarm. if the alarm has sounded and (due to a takeback or time control
13325      * with increment) the time remaining has increased to a level above the
13326      * threshold, reset the alarm so it can sound again. 
13327      */
13328     
13329     if (appData.icsActive && appData.icsAlarm) {
13330
13331         /* make sure we are dealing with the user's clock */
13332         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13333                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13334            )) return;
13335
13336         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13337             alarmSounded = FALSE;
13338         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13339             PlayAlarmSound();
13340             alarmSounded = TRUE;
13341         }
13342     }
13343 }
13344
13345
13346 /* A player has just moved, so stop the previously running
13347    clock and (if in clock mode) start the other one.
13348    We redisplay both clocks in case we're in ICS mode, because
13349    ICS gives us an update to both clocks after every move.
13350    Note that this routine is called *after* forwardMostMove
13351    is updated, so the last fractional tick must be subtracted
13352    from the color that is *not* on move now.
13353 */
13354 void
13355 SwitchClocks()
13356 {
13357     long lastTickLength;
13358     TimeMark now;
13359     int flagged = FALSE;
13360
13361     GetTimeMark(&now);
13362
13363     if (StopClockTimer() && appData.clockMode) {
13364         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13365         if (WhiteOnMove(forwardMostMove)) {
13366             if(blackNPS >= 0) lastTickLength = 0;
13367             blackTimeRemaining -= lastTickLength;
13368            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13369 //         if(pvInfoList[forwardMostMove-1].time == -1)
13370                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13371                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13372         } else {
13373            if(whiteNPS >= 0) lastTickLength = 0;
13374            whiteTimeRemaining -= lastTickLength;
13375            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13376 //         if(pvInfoList[forwardMostMove-1].time == -1)
13377                  pvInfoList[forwardMostMove-1].time = 
13378                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13379         }
13380         flagged = CheckFlags();
13381     }
13382     CheckTimeControl();
13383
13384     if (flagged || !appData.clockMode) return;
13385
13386     switch (gameMode) {
13387       case MachinePlaysBlack:
13388       case MachinePlaysWhite:
13389       case BeginningOfGame:
13390         if (pausing) return;
13391         break;
13392
13393       case EditGame:
13394       case PlayFromGameFile:
13395       case IcsExamining:
13396         return;
13397
13398       default:
13399         break;
13400     }
13401
13402     tickStartTM = now;
13403     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13404       whiteTimeRemaining : blackTimeRemaining);
13405     StartClockTimer(intendedTickLength);
13406 }
13407         
13408
13409 /* Stop both clocks */
13410 void
13411 StopClocks()
13412 {       
13413     long lastTickLength;
13414     TimeMark now;
13415
13416     if (!StopClockTimer()) return;
13417     if (!appData.clockMode) return;
13418
13419     GetTimeMark(&now);
13420
13421     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13422     if (WhiteOnMove(forwardMostMove)) {
13423         if(whiteNPS >= 0) lastTickLength = 0;
13424         whiteTimeRemaining -= lastTickLength;
13425         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13426     } else {
13427         if(blackNPS >= 0) lastTickLength = 0;
13428         blackTimeRemaining -= lastTickLength;
13429         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13430     }
13431     CheckFlags();
13432 }
13433         
13434 /* Start clock of player on move.  Time may have been reset, so
13435    if clock is already running, stop and restart it. */
13436 void
13437 StartClocks()
13438 {
13439     (void) StopClockTimer(); /* in case it was running already */
13440     DisplayBothClocks();
13441     if (CheckFlags()) return;
13442
13443     if (!appData.clockMode) return;
13444     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13445
13446     GetTimeMark(&tickStartTM);
13447     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13448       whiteTimeRemaining : blackTimeRemaining);
13449
13450    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13451     whiteNPS = blackNPS = -1; 
13452     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13453        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13454         whiteNPS = first.nps;
13455     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13456        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13457         blackNPS = first.nps;
13458     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13459         whiteNPS = second.nps;
13460     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13461         blackNPS = second.nps;
13462     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13463
13464     StartClockTimer(intendedTickLength);
13465 }
13466
13467 char *
13468 TimeString(ms)
13469      long ms;
13470 {
13471     long second, minute, hour, day;
13472     char *sign = "";
13473     static char buf[32];
13474     
13475     if (ms > 0 && ms <= 9900) {
13476       /* convert milliseconds to tenths, rounding up */
13477       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13478
13479       sprintf(buf, " %03.1f ", tenths/10.0);
13480       return buf;
13481     }
13482
13483     /* convert milliseconds to seconds, rounding up */
13484     /* use floating point to avoid strangeness of integer division
13485        with negative dividends on many machines */
13486     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13487
13488     if (second < 0) {
13489         sign = "-";
13490         second = -second;
13491     }
13492     
13493     day = second / (60 * 60 * 24);
13494     second = second % (60 * 60 * 24);
13495     hour = second / (60 * 60);
13496     second = second % (60 * 60);
13497     minute = second / 60;
13498     second = second % 60;
13499     
13500     if (day > 0)
13501       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13502               sign, day, hour, minute, second);
13503     else if (hour > 0)
13504       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13505     else
13506       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13507     
13508     return buf;
13509 }
13510
13511
13512 /*
13513  * This is necessary because some C libraries aren't ANSI C compliant yet.
13514  */
13515 char *
13516 StrStr(string, match)
13517      char *string, *match;
13518 {
13519     int i, length;
13520     
13521     length = strlen(match);
13522     
13523     for (i = strlen(string) - length; i >= 0; i--, string++)
13524       if (!strncmp(match, string, length))
13525         return string;
13526     
13527     return NULL;
13528 }
13529
13530 char *
13531 StrCaseStr(string, match)
13532      char *string, *match;
13533 {
13534     int i, j, length;
13535     
13536     length = strlen(match);
13537     
13538     for (i = strlen(string) - length; i >= 0; i--, string++) {
13539         for (j = 0; j < length; j++) {
13540             if (ToLower(match[j]) != ToLower(string[j]))
13541               break;
13542         }
13543         if (j == length) return string;
13544     }
13545
13546     return NULL;
13547 }
13548
13549 #ifndef _amigados
13550 int
13551 StrCaseCmp(s1, s2)
13552      char *s1, *s2;
13553 {
13554     char c1, c2;
13555     
13556     for (;;) {
13557         c1 = ToLower(*s1++);
13558         c2 = ToLower(*s2++);
13559         if (c1 > c2) return 1;
13560         if (c1 < c2) return -1;
13561         if (c1 == NULLCHAR) return 0;
13562     }
13563 }
13564
13565
13566 int
13567 ToLower(c)
13568      int c;
13569 {
13570     return isupper(c) ? tolower(c) : c;
13571 }
13572
13573
13574 int
13575 ToUpper(c)
13576      int c;
13577 {
13578     return islower(c) ? toupper(c) : c;
13579 }
13580 #endif /* !_amigados    */
13581
13582 char *
13583 StrSave(s)
13584      char *s;
13585 {
13586     char *ret;
13587
13588     if ((ret = (char *) malloc(strlen(s) + 1))) {
13589         strcpy(ret, s);
13590     }
13591     return ret;
13592 }
13593
13594 char *
13595 StrSavePtr(s, savePtr)
13596      char *s, **savePtr;
13597 {
13598     if (*savePtr) {
13599         free(*savePtr);
13600     }
13601     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13602         strcpy(*savePtr, s);
13603     }
13604     return(*savePtr);
13605 }
13606
13607 char *
13608 PGNDate()
13609 {
13610     time_t clock;
13611     struct tm *tm;
13612     char buf[MSG_SIZ];
13613
13614     clock = time((time_t *)NULL);
13615     tm = localtime(&clock);
13616     sprintf(buf, "%04d.%02d.%02d",
13617             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13618     return StrSave(buf);
13619 }
13620
13621
13622 char *
13623 PositionToFEN(move, overrideCastling)
13624      int move;
13625      char *overrideCastling;
13626 {
13627     int i, j, fromX, fromY, toX, toY;
13628     int whiteToPlay;
13629     char buf[128];
13630     char *p, *q;
13631     int emptycount;
13632     ChessSquare piece;
13633
13634     whiteToPlay = (gameMode == EditPosition) ?
13635       !blackPlaysFirst : (move % 2 == 0);
13636     p = buf;
13637
13638     /* Piece placement data */
13639     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13640         emptycount = 0;
13641         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13642             if (boards[move][i][j] == EmptySquare) {
13643                 emptycount++;
13644             } else { ChessSquare piece = boards[move][i][j];
13645                 if (emptycount > 0) {
13646                     if(emptycount<10) /* [HGM] can be >= 10 */
13647                         *p++ = '0' + emptycount;
13648                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13649                     emptycount = 0;
13650                 }
13651                 if(PieceToChar(piece) == '+') {
13652                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13653                     *p++ = '+';
13654                     piece = (ChessSquare)(DEMOTED piece);
13655                 } 
13656                 *p++ = PieceToChar(piece);
13657                 if(p[-1] == '~') {
13658                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13659                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13660                     *p++ = '~';
13661                 }
13662             }
13663         }
13664         if (emptycount > 0) {
13665             if(emptycount<10) /* [HGM] can be >= 10 */
13666                 *p++ = '0' + emptycount;
13667             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13668             emptycount = 0;
13669         }
13670         *p++ = '/';
13671     }
13672     *(p - 1) = ' ';
13673
13674     /* [HGM] print Crazyhouse or Shogi holdings */
13675     if( gameInfo.holdingsWidth ) {
13676         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13677         q = p;
13678         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13679             piece = boards[move][i][BOARD_WIDTH-1];
13680             if( piece != EmptySquare )
13681               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13682                   *p++ = PieceToChar(piece);
13683         }
13684         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13685             piece = boards[move][BOARD_HEIGHT-i-1][0];
13686             if( piece != EmptySquare )
13687               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13688                   *p++ = PieceToChar(piece);
13689         }
13690
13691         if( q == p ) *p++ = '-';
13692         *p++ = ']';
13693         *p++ = ' ';
13694     }
13695
13696     /* Active color */
13697     *p++ = whiteToPlay ? 'w' : 'b';
13698     *p++ = ' ';
13699
13700   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13701     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13702   } else {
13703   if(nrCastlingRights) {
13704      q = p;
13705      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13706        /* [HGM] write directly from rights */
13707            if(castlingRights[move][2] >= 0 &&
13708               castlingRights[move][0] >= 0   )
13709                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13710            if(castlingRights[move][2] >= 0 &&
13711               castlingRights[move][1] >= 0   )
13712                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13713            if(castlingRights[move][5] >= 0 &&
13714               castlingRights[move][3] >= 0   )
13715                 *p++ = castlingRights[move][3] + AAA;
13716            if(castlingRights[move][5] >= 0 &&
13717               castlingRights[move][4] >= 0   )
13718                 *p++ = castlingRights[move][4] + AAA;
13719      } else {
13720
13721         /* [HGM] write true castling rights */
13722         if( nrCastlingRights == 6 ) {
13723             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13724                castlingRights[move][2] >= 0  ) *p++ = 'K';
13725             if(castlingRights[move][1] == BOARD_LEFT &&
13726                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13727             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13728                castlingRights[move][5] >= 0  ) *p++ = 'k';
13729             if(castlingRights[move][4] == BOARD_LEFT &&
13730                castlingRights[move][5] >= 0  ) *p++ = 'q';
13731         }
13732      }
13733      if (q == p) *p++ = '-'; /* No castling rights */
13734      *p++ = ' ';
13735   }
13736
13737   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13738      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13739     /* En passant target square */
13740     if (move > backwardMostMove) {
13741         fromX = moveList[move - 1][0] - AAA;
13742         fromY = moveList[move - 1][1] - ONE;
13743         toX = moveList[move - 1][2] - AAA;
13744         toY = moveList[move - 1][3] - ONE;
13745         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13746             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13747             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13748             fromX == toX) {
13749             /* 2-square pawn move just happened */
13750             *p++ = toX + AAA;
13751             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13752         } else {
13753             *p++ = '-';
13754         }
13755     } else if(move == backwardMostMove) {
13756         // [HGM] perhaps we should always do it like this, and forget the above?
13757         if(epStatus[move] >= 0) {
13758             *p++ = epStatus[move] + AAA;
13759             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13760         } else {
13761             *p++ = '-';
13762         }
13763     } else {
13764         *p++ = '-';
13765     }
13766     *p++ = ' ';
13767   }
13768   }
13769
13770     /* [HGM] find reversible plies */
13771     {   int i = 0, j=move;
13772
13773         if (appData.debugMode) { int k;
13774             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13775             for(k=backwardMostMove; k<=forwardMostMove; k++)
13776                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13777
13778         }
13779
13780         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13781         if( j == backwardMostMove ) i += initialRulePlies;
13782         sprintf(p, "%d ", i);
13783         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13784     }
13785     /* Fullmove number */
13786     sprintf(p, "%d", (move / 2) + 1);
13787     
13788     return StrSave(buf);
13789 }
13790
13791 Boolean
13792 ParseFEN(board, blackPlaysFirst, fen)
13793     Board board;
13794      int *blackPlaysFirst;
13795      char *fen;
13796 {
13797     int i, j;
13798     char *p;
13799     int emptycount;
13800     ChessSquare piece;
13801
13802     p = fen;
13803
13804     /* [HGM] by default clear Crazyhouse holdings, if present */
13805     if(gameInfo.holdingsWidth) {
13806        for(i=0; i<BOARD_HEIGHT; i++) {
13807            board[i][0]             = EmptySquare; /* black holdings */
13808            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13809            board[i][1]             = (ChessSquare) 0; /* black counts */
13810            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13811        }
13812     }
13813
13814     /* Piece placement data */
13815     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13816         j = 0;
13817         for (;;) {
13818             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13819                 if (*p == '/') p++;
13820                 emptycount = gameInfo.boardWidth - j;
13821                 while (emptycount--)
13822                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13823                 break;
13824 #if(BOARD_SIZE >= 10)
13825             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13826                 p++; emptycount=10;
13827                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13828                 while (emptycount--)
13829                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13830 #endif
13831             } else if (isdigit(*p)) {
13832                 emptycount = *p++ - '0';
13833                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13834                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13835                 while (emptycount--)
13836                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13837             } else if (*p == '+' || isalpha(*p)) {
13838                 if (j >= gameInfo.boardWidth) return FALSE;
13839                 if(*p=='+') {
13840                     piece = CharToPiece(*++p);
13841                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13842                     piece = (ChessSquare) (PROMOTED piece ); p++;
13843                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13844                 } else piece = CharToPiece(*p++);
13845
13846                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13847                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13848                     piece = (ChessSquare) (PROMOTED piece);
13849                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13850                     p++;
13851                 }
13852                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13853             } else {
13854                 return FALSE;
13855             }
13856         }
13857     }
13858     while (*p == '/' || *p == ' ') p++;
13859
13860     /* [HGM] look for Crazyhouse holdings here */
13861     while(*p==' ') p++;
13862     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13863         if(*p == '[') p++;
13864         if(*p == '-' ) *p++; /* empty holdings */ else {
13865             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13866             /* if we would allow FEN reading to set board size, we would   */
13867             /* have to add holdings and shift the board read so far here   */
13868             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13869                 *p++;
13870                 if((int) piece >= (int) BlackPawn ) {
13871                     i = (int)piece - (int)BlackPawn;
13872                     i = PieceToNumber((ChessSquare)i);
13873                     if( i >= gameInfo.holdingsSize ) return FALSE;
13874                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13875                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13876                 } else {
13877                     i = (int)piece - (int)WhitePawn;
13878                     i = PieceToNumber((ChessSquare)i);
13879                     if( i >= gameInfo.holdingsSize ) return FALSE;
13880                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13881                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13882                 }
13883             }
13884         }
13885         if(*p == ']') *p++;
13886     }
13887
13888     while(*p == ' ') p++;
13889
13890     /* Active color */
13891     switch (*p++) {
13892       case 'w':
13893         *blackPlaysFirst = FALSE;
13894         break;
13895       case 'b': 
13896         *blackPlaysFirst = TRUE;
13897         break;
13898       default:
13899         return FALSE;
13900     }
13901
13902     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13903     /* return the extra info in global variiables             */
13904
13905     /* set defaults in case FEN is incomplete */
13906     FENepStatus = EP_UNKNOWN;
13907     for(i=0; i<nrCastlingRights; i++ ) {
13908         FENcastlingRights[i] =
13909             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13910     }   /* assume possible unless obviously impossible */
13911     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13912     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13913     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13914                            && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13915     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13916     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13917     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13918                            && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13919     FENrulePlies = 0;
13920
13921     while(*p==' ') p++;
13922     if(nrCastlingRights) {
13923       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13924           /* castling indicator present, so default becomes no castlings */
13925           for(i=0; i<nrCastlingRights; i++ ) {
13926                  FENcastlingRights[i] = -1;
13927           }
13928       }
13929       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13930              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13931              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13932              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13933         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13934
13935         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13936             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13937             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13938         }
13939         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13940             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13941         if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13942                            && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13943         if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13944                            && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13945         switch(c) {
13946           case'K':
13947               for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13948               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13949               FENcastlingRights[2] = whiteKingFile;
13950               break;
13951           case'Q':
13952               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13953               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13954               FENcastlingRights[2] = whiteKingFile;
13955               break;
13956           case'k':
13957               for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13958               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13959               FENcastlingRights[5] = blackKingFile;
13960               break;
13961           case'q':
13962               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13963               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13964               FENcastlingRights[5] = blackKingFile;
13965           case '-':
13966               break;
13967           default: /* FRC castlings */
13968               if(c >= 'a') { /* black rights */
13969                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13970                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13971                   if(i == BOARD_RGHT) break;
13972                   FENcastlingRights[5] = i;
13973                   c -= AAA;
13974                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13975                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13976                   if(c > i)
13977                       FENcastlingRights[3] = c;
13978                   else
13979                       FENcastlingRights[4] = c;
13980               } else { /* white rights */
13981                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13982                     if(board[0][i] == WhiteKing) break;
13983                   if(i == BOARD_RGHT) break;
13984                   FENcastlingRights[2] = i;
13985                   c -= AAA - 'a' + 'A';
13986                   if(board[0][c] >= WhiteKing) break;
13987                   if(c > i)
13988                       FENcastlingRights[0] = c;
13989                   else
13990                       FENcastlingRights[1] = c;
13991               }
13992         }
13993       }
13994       for(i=0; i<nrCastlingRights; i++)
13995         if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
13996     if (appData.debugMode) {
13997         fprintf(debugFP, "FEN castling rights:");
13998         for(i=0; i<nrCastlingRights; i++)
13999         fprintf(debugFP, " %d", FENcastlingRights[i]);
14000         fprintf(debugFP, "\n");
14001     }
14002
14003       while(*p==' ') p++;
14004     }
14005
14006     /* read e.p. field in games that know e.p. capture */
14007     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14008        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14009       if(*p=='-') {
14010         p++; FENepStatus = EP_NONE;
14011       } else {
14012          char c = *p++ - AAA;
14013
14014          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14015          if(*p >= '0' && *p <='9') *p++;
14016          FENepStatus = c;
14017       }
14018     }
14019
14020
14021     if(sscanf(p, "%d", &i) == 1) {
14022         FENrulePlies = i; /* 50-move ply counter */
14023         /* (The move number is still ignored)    */
14024     }
14025
14026     return TRUE;
14027 }
14028       
14029 void
14030 EditPositionPasteFEN(char *fen)
14031 {
14032   if (fen != NULL) {
14033     Board initial_position;
14034
14035     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14036       DisplayError(_("Bad FEN position in clipboard"), 0);
14037       return ;
14038     } else {
14039       int savedBlackPlaysFirst = blackPlaysFirst;
14040       EditPositionEvent();
14041       blackPlaysFirst = savedBlackPlaysFirst;
14042       CopyBoard(boards[0], initial_position);
14043           /* [HGM] copy FEN attributes as well */
14044           {   int i;
14045               initialRulePlies = FENrulePlies;
14046               epStatus[0] = FENepStatus;
14047               for( i=0; i<nrCastlingRights; i++ )
14048                   castlingRights[0][i] = FENcastlingRights[i];
14049           }
14050       EditPositionDone(FALSE);
14051       DisplayBothClocks();
14052       DrawPosition(FALSE, boards[currentMove]);
14053     }
14054   }
14055 }
14056
14057 static char cseq[12] = "\\   ";
14058
14059 Boolean set_cont_sequence(char *new_seq)
14060 {
14061     int len;
14062     Boolean ret;
14063
14064     // handle bad attempts to set the sequence
14065         if (!new_seq)
14066                 return 0; // acceptable error - no debug
14067
14068     len = strlen(new_seq);
14069     ret = (len > 0) && (len < sizeof(cseq));
14070     if (ret)
14071         strcpy(cseq, new_seq);
14072     else if (appData.debugMode)
14073         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14074     return ret;
14075 }
14076
14077 /*
14078     reformat a source message so words don't cross the width boundary.  internal
14079     newlines are not removed.  returns the wrapped size (no null character unless
14080     included in source message).  If dest is NULL, only calculate the size required
14081     for the dest buffer.  lp argument indicats line position upon entry, and it's
14082     passed back upon exit.
14083 */
14084 int wrap(char *dest, char *src, int count, int width, int *lp)
14085 {
14086     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14087
14088     cseq_len = strlen(cseq);
14089     old_line = line = *lp;
14090     ansi = len = clen = 0;
14091
14092     for (i=0; i < count; i++)
14093     {
14094         if (src[i] == '\033')
14095             ansi = 1;
14096
14097         // if we hit the width, back up
14098         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14099         {
14100             // store i & len in case the word is too long
14101             old_i = i, old_len = len;
14102
14103             // find the end of the last word
14104             while (i && src[i] != ' ' && src[i] != '\n')
14105             {
14106                 i--;
14107                 len--;
14108             }
14109
14110             // word too long?  restore i & len before splitting it
14111             if ((old_i-i+clen) >= width)
14112             {
14113                 i = old_i;
14114                 len = old_len;
14115             }
14116
14117             // extra space?
14118             if (i && src[i-1] == ' ')
14119                 len--;
14120
14121             if (src[i] != ' ' && src[i] != '\n')
14122             {
14123                 i--;
14124                 if (len)
14125                     len--;
14126             }
14127
14128             // now append the newline and continuation sequence
14129             if (dest)
14130                 dest[len] = '\n';
14131             len++;
14132             if (dest)
14133                 strncpy(dest+len, cseq, cseq_len);
14134             len += cseq_len;
14135             line = cseq_len;
14136             clen = cseq_len;
14137             continue;
14138         }
14139
14140         if (dest)
14141             dest[len] = src[i];
14142         len++;
14143         if (!ansi)
14144             line++;
14145         if (src[i] == '\n')
14146             line = 0;
14147         if (src[i] == 'm')
14148             ansi = 0;
14149     }
14150     if (dest && appData.debugMode)
14151     {
14152         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14153             count, width, line, len, *lp);
14154         show_bytes(debugFP, src, count);
14155         fprintf(debugFP, "\ndest: ");
14156         show_bytes(debugFP, dest, len);
14157         fprintf(debugFP, "\n");
14158     }
14159     *lp = dest ? line : old_line;
14160
14161     return len;
14162 }