8cf3a43ae56541c4b4ad8701727d881c4a286cbe
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char  initialRights[BOARD_FILES];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
449 int loadFlag = 0; 
450 int shuffleOpenings;
451 int mute; // mute all sounds
452
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
456 int storedGames = 0;
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
462
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((void));
465 void CleanupTail P((void));
466
467 ChessSquare  FIDEArray[2][BOARD_FILES] = {
468     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471         BlackKing, BlackBishop, BlackKnight, BlackRook }
472 };
473
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackKing, BlackKnight, BlackRook }
479 };
480
481 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484     { BlackRook, BlackMan, BlackBishop, BlackQueen,
485         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
486 };
487
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492         BlackKing, BlackBishop, BlackKnight, BlackRook }
493 };
494
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
500 };
501
502
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
509 };
510
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
516 };
517
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
520         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
522         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
523 };
524
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
527         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
529         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
530 };
531
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
534         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
536         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
537 };
538
539 #ifdef GOTHIC
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
542         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
544         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !GOTHIC
547 #define GothicArray CapablancaArray
548 #endif // !GOTHIC
549
550 #ifdef FALCON
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
553         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
555         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !FALCON
558 #define FalconArray CapablancaArray
559 #endif // !FALCON
560
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
567
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
574 };
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
578
579
580 Board initialPosition;
581
582
583 /* Convert str to a rating. Checks for special cases of "----",
584
585    "++++", etc. Also strips ()'s */
586 int
587 string_to_rating(str)
588   char *str;
589 {
590   while(*str && !isdigit(*str)) ++str;
591   if (!*str)
592     return 0;   /* One of the special "no rating" cases */
593   else
594     return atoi(str);
595 }
596
597 void
598 ClearProgramStats()
599 {
600     /* Init programStats */
601     programStats.movelist[0] = 0;
602     programStats.depth = 0;
603     programStats.nr_moves = 0;
604     programStats.moves_left = 0;
605     programStats.nodes = 0;
606     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
607     programStats.score = 0;
608     programStats.got_only_move = 0;
609     programStats.got_fail = 0;
610     programStats.line_is_book = 0;
611 }
612
613 void
614 InitBackEnd1()
615 {
616     int matched, min, sec;
617
618     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
619
620     GetTimeMark(&programStartTime);
621     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
622
623     ClearProgramStats();
624     programStats.ok_to_send = 1;
625     programStats.seen_stat = 0;
626
627     /*
628      * Initialize game list
629      */
630     ListNew(&gameList);
631
632
633     /*
634      * Internet chess server status
635      */
636     if (appData.icsActive) {
637         appData.matchMode = FALSE;
638         appData.matchGames = 0;
639 #if ZIPPY       
640         appData.noChessProgram = !appData.zippyPlay;
641 #else
642         appData.zippyPlay = FALSE;
643         appData.zippyTalk = FALSE;
644         appData.noChessProgram = TRUE;
645 #endif
646         if (*appData.icsHelper != NULLCHAR) {
647             appData.useTelnet = TRUE;
648             appData.telnetProgram = appData.icsHelper;
649         }
650     } else {
651         appData.zippyTalk = appData.zippyPlay = FALSE;
652     }
653
654     /* [AS] Initialize pv info list [HGM] and game state */
655     {
656         int i, j;
657
658         for( i=0; i<=framePtr; i++ ) {
659             pvInfoList[i].depth = -1;
660             boards[i][EP_STATUS] = EP_NONE;
661             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
662         }
663     }
664
665     /*
666      * Parse timeControl resource
667      */
668     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669                           appData.movesPerSession)) {
670         char buf[MSG_SIZ];
671         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672         DisplayFatalError(buf, 0, 2);
673     }
674
675     /*
676      * Parse searchTime resource
677      */
678     if (*appData.searchTime != NULLCHAR) {
679         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
680         if (matched == 1) {
681             searchTime = min * 60;
682         } else if (matched == 2) {
683             searchTime = min * 60 + sec;
684         } else {
685             char buf[MSG_SIZ];
686             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687             DisplayFatalError(buf, 0, 2);
688         }
689     }
690
691     /* [AS] Adjudication threshold */
692     adjudicateLossThreshold = appData.adjudicateLossThreshold;
693     
694     first.which = "first";
695     second.which = "second";
696     first.maybeThinking = second.maybeThinking = FALSE;
697     first.pr = second.pr = NoProc;
698     first.isr = second.isr = NULL;
699     first.sendTime = second.sendTime = 2;
700     first.sendDrawOffers = 1;
701     if (appData.firstPlaysBlack) {
702         first.twoMachinesColor = "black\n";
703         second.twoMachinesColor = "white\n";
704     } else {
705         first.twoMachinesColor = "white\n";
706         second.twoMachinesColor = "black\n";
707     }
708     first.program = appData.firstChessProgram;
709     second.program = appData.secondChessProgram;
710     first.host = appData.firstHost;
711     second.host = appData.secondHost;
712     first.dir = appData.firstDirectory;
713     second.dir = appData.secondDirectory;
714     first.other = &second;
715     second.other = &first;
716     first.initString = appData.initString;
717     second.initString = appData.secondInitString;
718     first.computerString = appData.firstComputerString;
719     second.computerString = appData.secondComputerString;
720     first.useSigint = second.useSigint = TRUE;
721     first.useSigterm = second.useSigterm = TRUE;
722     first.reuse = appData.reuseFirst;
723     second.reuse = appData.reuseSecond;
724     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
725     second.nps = appData.secondNPS;
726     first.useSetboard = second.useSetboard = FALSE;
727     first.useSAN = second.useSAN = FALSE;
728     first.usePing = second.usePing = FALSE;
729     first.lastPing = second.lastPing = 0;
730     first.lastPong = second.lastPong = 0;
731     first.usePlayother = second.usePlayother = FALSE;
732     first.useColors = second.useColors = TRUE;
733     first.useUsermove = second.useUsermove = FALSE;
734     first.sendICS = second.sendICS = FALSE;
735     first.sendName = second.sendName = appData.icsActive;
736     first.sdKludge = second.sdKludge = FALSE;
737     first.stKludge = second.stKludge = FALSE;
738     TidyProgramName(first.program, first.host, first.tidy);
739     TidyProgramName(second.program, second.host, second.tidy);
740     first.matchWins = second.matchWins = 0;
741     strcpy(first.variants, appData.variant);
742     strcpy(second.variants, appData.variant);
743     first.analysisSupport = second.analysisSupport = 2; /* detect */
744     first.analyzing = second.analyzing = FALSE;
745     first.initDone = second.initDone = FALSE;
746
747     /* New features added by Tord: */
748     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750     /* End of new features added by Tord. */
751     first.fenOverride  = appData.fenOverride1;
752     second.fenOverride = appData.fenOverride2;
753
754     /* [HGM] time odds: set factor for each machine */
755     first.timeOdds  = appData.firstTimeOdds;
756     second.timeOdds = appData.secondTimeOdds;
757     { int norm = 1;
758         if(appData.timeOddsMode) {
759             norm = first.timeOdds;
760             if(norm > second.timeOdds) norm = second.timeOdds;
761         }
762         first.timeOdds /= norm;
763         second.timeOdds /= norm;
764     }
765
766     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767     first.accumulateTC = appData.firstAccumulateTC;
768     second.accumulateTC = appData.secondAccumulateTC;
769     first.maxNrOfSessions = second.maxNrOfSessions = 1;
770
771     /* [HGM] debug */
772     first.debug = second.debug = FALSE;
773     first.supportsNPS = second.supportsNPS = UNKNOWN;
774
775     /* [HGM] options */
776     first.optionSettings  = appData.firstOptions;
777     second.optionSettings = appData.secondOptions;
778
779     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781     first.isUCI = appData.firstIsUCI; /* [AS] */
782     second.isUCI = appData.secondIsUCI; /* [AS] */
783     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
785
786     if (appData.firstProtocolVersion > PROTOVER ||
787         appData.firstProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.firstProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       first.protocolVersion = appData.firstProtocolVersion;
794     }
795
796     if (appData.secondProtocolVersion > PROTOVER ||
797         appData.secondProtocolVersion < 1) {
798       char buf[MSG_SIZ];
799       sprintf(buf, _("protocol version %d not supported"),
800               appData.secondProtocolVersion);
801       DisplayFatalError(buf, 0, 2);
802     } else {
803       second.protocolVersion = appData.secondProtocolVersion;
804     }
805
806     if (appData.icsActive) {
807         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
808 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810         appData.clockMode = FALSE;
811         first.sendTime = second.sendTime = 0;
812     }
813     
814 #if ZIPPY
815     /* Override some settings from environment variables, for backward
816        compatibility.  Unfortunately it's not feasible to have the env
817        vars just set defaults, at least in xboard.  Ugh.
818     */
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
820       ZippyInit();
821     }
822 #endif
823     
824     if (appData.noChessProgram) {
825         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826         sprintf(programVersion, "%s", PACKAGE_STRING);
827     } else {
828       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
831     }
832
833     if (!appData.icsActive) {
834       char buf[MSG_SIZ];
835       /* Check for variants that are supported only in ICS mode,
836          or not at all.  Some that are accepted here nevertheless
837          have bugs; see comments below.
838       */
839       VariantClass variant = StringToVariant(appData.variant);
840       switch (variant) {
841       case VariantBughouse:     /* need four players and two boards */
842       case VariantKriegspiel:   /* need to hide pieces and move details */
843       /* case VariantFischeRandom: (Fabien: moved below) */
844         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845         DisplayFatalError(buf, 0, 2);
846         return;
847
848       case VariantUnknown:
849       case VariantLoadable:
850       case Variant29:
851       case Variant30:
852       case Variant31:
853       case Variant32:
854       case Variant33:
855       case Variant34:
856       case Variant35:
857       case Variant36:
858       default:
859         sprintf(buf, _("Unknown variant name %s"), appData.variant);
860         DisplayFatalError(buf, 0, 2);
861         return;
862
863       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
864       case VariantFairy:      /* [HGM] TestLegality definitely off! */
865       case VariantGothic:     /* [HGM] should work */
866       case VariantCapablanca: /* [HGM] should work */
867       case VariantCourier:    /* [HGM] initial forced moves not implemented */
868       case VariantShogi:      /* [HGM] drops not tested for legality */
869       case VariantKnightmate: /* [HGM] should work */
870       case VariantCylinder:   /* [HGM] untested */
871       case VariantFalcon:     /* [HGM] untested */
872       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873                                  offboard interposition not understood */
874       case VariantNormal:     /* definitely works! */
875       case VariantWildCastle: /* pieces not automatically shuffled */
876       case VariantNoCastle:   /* pieces not automatically shuffled */
877       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878       case VariantLosers:     /* should work except for win condition,
879                                  and doesn't know captures are mandatory */
880       case VariantSuicide:    /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantGiveaway:   /* should work except for win condition,
883                                  and doesn't know captures are mandatory */
884       case VariantTwoKings:   /* should work */
885       case VariantAtomic:     /* should work except for win condition */
886       case Variant3Check:     /* should work except for win condition */
887       case VariantShatranj:   /* should work except for all win conditions */
888       case VariantBerolina:   /* might work if TestLegality is off */
889       case VariantCapaRandom: /* should work */
890       case VariantJanus:      /* should work */
891       case VariantSuper:      /* experimental */
892       case VariantGreat:      /* experimental, requires legality testing to be off */
893         break;
894       }
895     }
896
897     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
898     InitEngineUCI( installDir, &second );
899 }
900
901 int NextIntegerFromString( char ** str, long * value )
902 {
903     int result = -1;
904     char * s = *str;
905
906     while( *s == ' ' || *s == '\t' ) {
907         s++;
908     }
909
910     *value = 0;
911
912     if( *s >= '0' && *s <= '9' ) {
913         while( *s >= '0' && *s <= '9' ) {
914             *value = *value * 10 + (*s - '0');
915             s++;
916         }
917
918         result = 0;
919     }
920
921     *str = s;
922
923     return result;
924 }
925
926 int NextTimeControlFromString( char ** str, long * value )
927 {
928     long temp;
929     int result = NextIntegerFromString( str, &temp );
930
931     if( result == 0 ) {
932         *value = temp * 60; /* Minutes */
933         if( **str == ':' ) {
934             (*str)++;
935             result = NextIntegerFromString( str, &temp );
936             *value += temp; /* Seconds */
937         }
938     }
939
940     return result;
941 }
942
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
945     int result = -1; long temp, temp2;
946
947     if(**str != '+') return -1; // old params remain in force!
948     (*str)++;
949     if( NextTimeControlFromString( str, &temp ) ) return -1;
950
951     if(**str != '/') {
952         /* time only: incremental or sudden-death time control */
953         if(**str == '+') { /* increment follows; read it */
954             (*str)++;
955             if(result = NextIntegerFromString( str, &temp2)) return -1;
956             *inc = temp2 * 1000;
957         } else *inc = 0;
958         *moves = 0; *tc = temp * 1000; 
959         return 0;
960     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
961
962     (*str)++; /* classical time control */
963     result = NextTimeControlFromString( str, &temp2);
964     if(result == 0) {
965         *moves = temp/60;
966         *tc    = temp2 * 1000;
967         *inc   = 0;
968     }
969     return result;
970 }
971
972 int GetTimeQuota(int movenr)
973 {   /* [HGM] get time to add from the multi-session time-control string */
974     int moves=1; /* kludge to force reading of first session */
975     long time, increment;
976     char *s = fullTimeControlString;
977
978     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
979     do {
980         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982         if(movenr == -1) return time;    /* last move before new session     */
983         if(!moves) return increment;     /* current session is incremental   */
984         if(movenr >= 0) movenr -= moves; /* we already finished this session */
985     } while(movenr >= -1);               /* try again for next session       */
986
987     return 0; // no new time quota on this move
988 }
989
990 int
991 ParseTimeControl(tc, ti, mps)
992      char *tc;
993      int ti;
994      int mps;
995 {
996   long tc1;
997   long tc2;
998   char buf[MSG_SIZ];
999   
1000   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1001   if(ti > 0) {
1002     if(mps)
1003       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004     else sprintf(buf, "+%s+%d", tc, ti);
1005   } else {
1006     if(mps)
1007              sprintf(buf, "+%d/%s", mps, tc);
1008     else sprintf(buf, "+%s", tc);
1009   }
1010   fullTimeControlString = StrSave(buf);
1011   
1012   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1013     return FALSE;
1014   }
1015   
1016   if( *tc == '/' ) {
1017     /* Parse second time control */
1018     tc++;
1019     
1020     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1021       return FALSE;
1022     }
1023     
1024     if( tc2 == 0 ) {
1025       return FALSE;
1026     }
1027     
1028     timeControl_2 = tc2 * 1000;
1029   }
1030   else {
1031     timeControl_2 = 0;
1032   }
1033   
1034   if( tc1 == 0 ) {
1035     return FALSE;
1036   }
1037   
1038   timeControl = tc1 * 1000;
1039   
1040   if (ti >= 0) {
1041     timeIncrement = ti * 1000;  /* convert to ms */
1042     movesPerSession = 0;
1043   } else {
1044     timeIncrement = 0;
1045     movesPerSession = mps;
1046   }
1047   return TRUE;
1048 }
1049
1050 void
1051 InitBackEnd2()
1052 {
1053     if (appData.debugMode) {
1054         fprintf(debugFP, "%s\n", programVersion);
1055     }
1056
1057     set_cont_sequence(appData.wrapContSeq);
1058     if (appData.matchGames > 0) {
1059         appData.matchMode = TRUE;
1060     } else if (appData.matchMode) {
1061         appData.matchGames = 1;
1062     }
1063     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064         appData.matchGames = appData.sameColorGames;
1065     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1068     }
1069     Reset(TRUE, FALSE);
1070     if (appData.noChessProgram || first.protocolVersion == 1) {
1071       InitBackEnd3();
1072     } else {
1073       /* kludge: allow timeout for initial "feature" commands */
1074       FreezeUI();
1075       DisplayMessage("", _("Starting chess program"));
1076       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1077     }
1078 }
1079
1080 void
1081 InitBackEnd3 P((void))
1082 {
1083     GameMode initialMode;
1084     char buf[MSG_SIZ];
1085     int err;
1086
1087     InitChessProgram(&first, startedFromSetupPosition);
1088
1089
1090     if (appData.icsActive) {
1091 #ifdef WIN32
1092         /* [DM] Make a console window if needed [HGM] merged ifs */
1093         ConsoleCreate(); 
1094 #endif
1095         err = establish();
1096         if (err != 0) {
1097             if (*appData.icsCommPort != NULLCHAR) {
1098                 sprintf(buf, _("Could not open comm port %s"),  
1099                         appData.icsCommPort);
1100             } else {
1101                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1102                         appData.icsHost, appData.icsPort);
1103             }
1104             DisplayFatalError(buf, err, 1);
1105             return;
1106         }
1107         SetICSMode();
1108         telnetISR =
1109           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1110         fromUserISR =
1111           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112     } else if (appData.noChessProgram) {
1113         SetNCPMode();
1114     } else {
1115         SetGNUMode();
1116     }
1117
1118     if (*appData.cmailGameName != NULLCHAR) {
1119         SetCmailMode();
1120         OpenLoopback(&cmailPR);
1121         cmailISR =
1122           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1123     }
1124     
1125     ThawUI();
1126     DisplayMessage("", "");
1127     if (StrCaseCmp(appData.initialMode, "") == 0) {
1128       initialMode = BeginningOfGame;
1129     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130       initialMode = TwoMachinesPlay;
1131     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132       initialMode = AnalyzeFile; 
1133     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134       initialMode = AnalyzeMode;
1135     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136       initialMode = MachinePlaysWhite;
1137     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138       initialMode = MachinePlaysBlack;
1139     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140       initialMode = EditGame;
1141     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142       initialMode = EditPosition;
1143     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144       initialMode = Training;
1145     } else {
1146       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147       DisplayFatalError(buf, 0, 2);
1148       return;
1149     }
1150
1151     if (appData.matchMode) {
1152         /* Set up machine vs. machine match */
1153         if (appData.noChessProgram) {
1154             DisplayFatalError(_("Can't have a match with no chess programs"),
1155                               0, 2);
1156             return;
1157         }
1158         matchMode = TRUE;
1159         matchGame = 1;
1160         if (*appData.loadGameFile != NULLCHAR) {
1161             int index = appData.loadGameIndex; // [HGM] autoinc
1162             if(index<0) lastIndex = index = 1;
1163             if (!LoadGameFromFile(appData.loadGameFile,
1164                                   index,
1165                                   appData.loadGameFile, FALSE)) {
1166                 DisplayFatalError(_("Bad game file"), 0, 1);
1167                 return;
1168             }
1169         } else if (*appData.loadPositionFile != NULLCHAR) {
1170             int index = appData.loadPositionIndex; // [HGM] autoinc
1171             if(index<0) lastIndex = index = 1;
1172             if (!LoadPositionFromFile(appData.loadPositionFile,
1173                                       index,
1174                                       appData.loadPositionFile)) {
1175                 DisplayFatalError(_("Bad position file"), 0, 1);
1176                 return;
1177             }
1178         }
1179         TwoMachinesEvent();
1180     } else if (*appData.cmailGameName != NULLCHAR) {
1181         /* Set up cmail mode */
1182         ReloadCmailMsgEvent(TRUE);
1183     } else {
1184         /* Set up other modes */
1185         if (initialMode == AnalyzeFile) {
1186           if (*appData.loadGameFile == NULLCHAR) {
1187             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1188             return;
1189           }
1190         }
1191         if (*appData.loadGameFile != NULLCHAR) {
1192             (void) LoadGameFromFile(appData.loadGameFile,
1193                                     appData.loadGameIndex,
1194                                     appData.loadGameFile, TRUE);
1195         } else if (*appData.loadPositionFile != NULLCHAR) {
1196             (void) LoadPositionFromFile(appData.loadPositionFile,
1197                                         appData.loadPositionIndex,
1198                                         appData.loadPositionFile);
1199             /* [HGM] try to make self-starting even after FEN load */
1200             /* to allow automatic setup of fairy variants with wtm */
1201             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202                 gameMode = BeginningOfGame;
1203                 setboardSpoiledMachineBlack = 1;
1204             }
1205             /* [HGM] loadPos: make that every new game uses the setup */
1206             /* from file as long as we do not switch variant          */
1207             if(!blackPlaysFirst) {
1208                 startedFromPositionFile = TRUE;
1209                 CopyBoard(filePosition, boards[0]);
1210             }
1211         }
1212         if (initialMode == AnalyzeMode) {
1213           if (appData.noChessProgram) {
1214             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1215             return;
1216           }
1217           if (appData.icsActive) {
1218             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1219             return;
1220           }
1221           AnalyzeModeEvent();
1222         } else if (initialMode == AnalyzeFile) {
1223           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224           ShowThinkingEvent();
1225           AnalyzeFileEvent();
1226           AnalysisPeriodicEvent(1);
1227         } else if (initialMode == MachinePlaysWhite) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineWhiteEvent();
1239         } else if (initialMode == MachinePlaysBlack) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           MachineBlackEvent();
1251         } else if (initialMode == TwoMachinesPlay) {
1252           if (appData.noChessProgram) {
1253             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1254                               0, 2);
1255             return;
1256           }
1257           if (appData.icsActive) {
1258             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1259                               0, 2);
1260             return;
1261           }
1262           TwoMachinesEvent();
1263         } else if (initialMode == EditGame) {
1264           EditGameEvent();
1265         } else if (initialMode == EditPosition) {
1266           EditPositionEvent();
1267         } else if (initialMode == Training) {
1268           if (*appData.loadGameFile == NULLCHAR) {
1269             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1270             return;
1271           }
1272           TrainingEvent();
1273         }
1274     }
1275 }
1276
1277 /*
1278  * Establish will establish a contact to a remote host.port.
1279  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280  *  used to talk to the host.
1281  * Returns 0 if okay, error code if not.
1282  */
1283 int
1284 establish()
1285 {
1286     char buf[MSG_SIZ];
1287
1288     if (*appData.icsCommPort != NULLCHAR) {
1289         /* Talk to the host through a serial comm port */
1290         return OpenCommPort(appData.icsCommPort, &icsPR);
1291
1292     } else if (*appData.gateway != NULLCHAR) {
1293         if (*appData.remoteShell == NULLCHAR) {
1294             /* Use the rcmd protocol to run telnet program on a gateway host */
1295             snprintf(buf, sizeof(buf), "%s %s %s",
1296                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1297             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1298
1299         } else {
1300             /* Use the rsh program to run telnet program on a gateway host */
1301             if (*appData.remoteUser == NULLCHAR) {
1302                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303                         appData.gateway, appData.telnetProgram,
1304                         appData.icsHost, appData.icsPort);
1305             } else {
1306                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307                         appData.remoteShell, appData.gateway, 
1308                         appData.remoteUser, appData.telnetProgram,
1309                         appData.icsHost, appData.icsPort);
1310             }
1311             return StartChildProcess(buf, "", &icsPR);
1312
1313         }
1314     } else if (appData.useTelnet) {
1315         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1316
1317     } else {
1318         /* TCP socket interface differs somewhat between
1319            Unix and NT; handle details in the front end.
1320            */
1321         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1322     }
1323 }
1324
1325 void
1326 show_bytes(fp, buf, count)
1327      FILE *fp;
1328      char *buf;
1329      int count;
1330 {
1331     while (count--) {
1332         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333             fprintf(fp, "\\%03o", *buf & 0xff);
1334         } else {
1335             putc(*buf, fp);
1336         }
1337         buf++;
1338     }
1339     fflush(fp);
1340 }
1341
1342 /* Returns an errno value */
1343 int
1344 OutputMaybeTelnet(pr, message, count, outError)
1345      ProcRef pr;
1346      char *message;
1347      int count;
1348      int *outError;
1349 {
1350     char buf[8192], *p, *q, *buflim;
1351     int left, newcount, outcount;
1352
1353     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354         *appData.gateway != NULLCHAR) {
1355         if (appData.debugMode) {
1356             fprintf(debugFP, ">ICS: ");
1357             show_bytes(debugFP, message, count);
1358             fprintf(debugFP, "\n");
1359         }
1360         return OutputToProcess(pr, message, count, outError);
1361     }
1362
1363     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1364     p = message;
1365     q = buf;
1366     left = count;
1367     newcount = 0;
1368     while (left) {
1369         if (q >= buflim) {
1370             if (appData.debugMode) {
1371                 fprintf(debugFP, ">ICS: ");
1372                 show_bytes(debugFP, buf, newcount);
1373                 fprintf(debugFP, "\n");
1374             }
1375             outcount = OutputToProcess(pr, buf, newcount, outError);
1376             if (outcount < newcount) return -1; /* to be sure */
1377             q = buf;
1378             newcount = 0;
1379         }
1380         if (*p == '\n') {
1381             *q++ = '\r';
1382             newcount++;
1383         } else if (((unsigned char) *p) == TN_IAC) {
1384             *q++ = (char) TN_IAC;
1385             newcount ++;
1386         }
1387         *q++ = *p++;
1388         newcount++;
1389         left--;
1390     }
1391     if (appData.debugMode) {
1392         fprintf(debugFP, ">ICS: ");
1393         show_bytes(debugFP, buf, newcount);
1394         fprintf(debugFP, "\n");
1395     }
1396     outcount = OutputToProcess(pr, buf, newcount, outError);
1397     if (outcount < newcount) return -1; /* to be sure */
1398     return count;
1399 }
1400
1401 void
1402 read_from_player(isr, closure, message, count, error)
1403      InputSourceRef isr;
1404      VOIDSTAR closure;
1405      char *message;
1406      int count;
1407      int error;
1408 {
1409     int outError, outCount;
1410     static int gotEof = 0;
1411
1412     /* Pass data read from player on to ICS */
1413     if (count > 0) {
1414         gotEof = 0;
1415         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416         if (outCount < count) {
1417             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1418         }
1419     } else if (count < 0) {
1420         RemoveInputSource(isr);
1421         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422     } else if (gotEof++ > 0) {
1423         RemoveInputSource(isr);
1424         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1425     }
1426 }
1427
1428 void
1429 KeepAlive()
1430 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431     SendToICS("date\n");
1432     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1433 }
1434
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1437 {
1438     char buffer[MSG_SIZ];
1439     va_list args;
1440
1441     va_start(args, format);
1442     vsnprintf(buffer, sizeof(buffer), format, args);
1443     buffer[sizeof(buffer)-1] = '\0';
1444     SendToICS(buffer);
1445     va_end(args);
1446 }
1447
1448 void
1449 SendToICS(s)
1450      char *s;
1451 {
1452     int count, outCount, outError;
1453
1454     if (icsPR == NULL) return;
1455
1456     count = strlen(s);
1457     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458     if (outCount < count) {
1459         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1460     }
1461 }
1462
1463 /* This is used for sending logon scripts to the ICS. Sending
1464    without a delay causes problems when using timestamp on ICC
1465    (at least on my machine). */
1466 void
1467 SendToICSDelayed(s,msdelay)
1468      char *s;
1469      long msdelay;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     if (appData.debugMode) {
1477         fprintf(debugFP, ">ICS: ");
1478         show_bytes(debugFP, s, count);
1479         fprintf(debugFP, "\n");
1480     }
1481     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1482                                       msdelay);
1483     if (outCount < count) {
1484         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1485     }
1486 }
1487
1488
1489 /* Remove all highlighting escape sequences in s
1490    Also deletes any suffix starting with '(' 
1491    */
1492 char *
1493 StripHighlightAndTitle(s)
1494      char *s;
1495 {
1496     static char retbuf[MSG_SIZ];
1497     char *p = retbuf;
1498
1499     while (*s != NULLCHAR) {
1500         while (*s == '\033') {
1501             while (*s != NULLCHAR && !isalpha(*s)) s++;
1502             if (*s != NULLCHAR) s++;
1503         }
1504         while (*s != NULLCHAR && *s != '\033') {
1505             if (*s == '(' || *s == '[') {
1506                 *p = NULLCHAR;
1507                 return retbuf;
1508             }
1509             *p++ = *s++;
1510         }
1511     }
1512     *p = NULLCHAR;
1513     return retbuf;
1514 }
1515
1516 /* Remove all highlighting escape sequences in s */
1517 char *
1518 StripHighlight(s)
1519      char *s;
1520 {
1521     static char retbuf[MSG_SIZ];
1522     char *p = retbuf;
1523
1524     while (*s != NULLCHAR) {
1525         while (*s == '\033') {
1526             while (*s != NULLCHAR && !isalpha(*s)) s++;
1527             if (*s != NULLCHAR) s++;
1528         }
1529         while (*s != NULLCHAR && *s != '\033') {
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 char *variantNames[] = VARIANT_NAMES;
1538 char *
1539 VariantName(v)
1540      VariantClass v;
1541 {
1542     return variantNames[v];
1543 }
1544
1545
1546 /* Identify a variant from the strings the chess servers use or the
1547    PGN Variant tag names we use. */
1548 VariantClass
1549 StringToVariant(e)
1550      char *e;
1551 {
1552     char *p;
1553     int wnum = -1;
1554     VariantClass v = VariantNormal;
1555     int i, found = FALSE;
1556     char buf[MSG_SIZ];
1557
1558     if (!e) return v;
1559
1560     /* [HGM] skip over optional board-size prefixes */
1561     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563         while( *e++ != '_');
1564     }
1565
1566     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1567         v = VariantNormal;
1568         found = TRUE;
1569     } else
1570     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571       if (StrCaseStr(e, variantNames[i])) {
1572         v = (VariantClass) i;
1573         found = TRUE;
1574         break;
1575       }
1576     }
1577
1578     if (!found) {
1579       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580           || StrCaseStr(e, "wild/fr") 
1581           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582         v = VariantFischeRandom;
1583       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584                  (i = 1, p = StrCaseStr(e, "w"))) {
1585         p += i;
1586         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1587         if (isdigit(*p)) {
1588           wnum = atoi(p);
1589         } else {
1590           wnum = -1;
1591         }
1592         switch (wnum) {
1593         case 0: /* FICS only, actually */
1594         case 1:
1595           /* Castling legal even if K starts on d-file */
1596           v = VariantWildCastle;
1597           break;
1598         case 2:
1599         case 3:
1600         case 4:
1601           /* Castling illegal even if K & R happen to start in
1602              normal positions. */
1603           v = VariantNoCastle;
1604           break;
1605         case 5:
1606         case 7:
1607         case 8:
1608         case 10:
1609         case 11:
1610         case 12:
1611         case 13:
1612         case 14:
1613         case 15:
1614         case 18:
1615         case 19:
1616           /* Castling legal iff K & R start in normal positions */
1617           v = VariantNormal;
1618           break;
1619         case 6:
1620         case 20:
1621         case 21:
1622           /* Special wilds for position setup; unclear what to do here */
1623           v = VariantLoadable;
1624           break;
1625         case 9:
1626           /* Bizarre ICC game */
1627           v = VariantTwoKings;
1628           break;
1629         case 16:
1630           v = VariantKriegspiel;
1631           break;
1632         case 17:
1633           v = VariantLosers;
1634           break;
1635         case 22:
1636           v = VariantFischeRandom;
1637           break;
1638         case 23:
1639           v = VariantCrazyhouse;
1640           break;
1641         case 24:
1642           v = VariantBughouse;
1643           break;
1644         case 25:
1645           v = Variant3Check;
1646           break;
1647         case 26:
1648           /* Not quite the same as FICS suicide! */
1649           v = VariantGiveaway;
1650           break;
1651         case 27:
1652           v = VariantAtomic;
1653           break;
1654         case 28:
1655           v = VariantShatranj;
1656           break;
1657
1658         /* Temporary names for future ICC types.  The name *will* change in 
1659            the next xboard/WinBoard release after ICC defines it. */
1660         case 29:
1661           v = Variant29;
1662           break;
1663         case 30:
1664           v = Variant30;
1665           break;
1666         case 31:
1667           v = Variant31;
1668           break;
1669         case 32:
1670           v = Variant32;
1671           break;
1672         case 33:
1673           v = Variant33;
1674           break;
1675         case 34:
1676           v = Variant34;
1677           break;
1678         case 35:
1679           v = Variant35;
1680           break;
1681         case 36:
1682           v = Variant36;
1683           break;
1684         case 37:
1685           v = VariantShogi;
1686           break;
1687         case 38:
1688           v = VariantXiangqi;
1689           break;
1690         case 39:
1691           v = VariantCourier;
1692           break;
1693         case 40:
1694           v = VariantGothic;
1695           break;
1696         case 41:
1697           v = VariantCapablanca;
1698           break;
1699         case 42:
1700           v = VariantKnightmate;
1701           break;
1702         case 43:
1703           v = VariantFairy;
1704           break;
1705         case 44:
1706           v = VariantCylinder;
1707           break;
1708         case 45:
1709           v = VariantFalcon;
1710           break;
1711         case 46:
1712           v = VariantCapaRandom;
1713           break;
1714         case 47:
1715           v = VariantBerolina;
1716           break;
1717         case 48:
1718           v = VariantJanus;
1719           break;
1720         case 49:
1721           v = VariantSuper;
1722           break;
1723         case 50:
1724           v = VariantGreat;
1725           break;
1726         case -1:
1727           /* Found "wild" or "w" in the string but no number;
1728              must assume it's normal chess. */
1729           v = VariantNormal;
1730           break;
1731         default:
1732           sprintf(buf, _("Unknown wild type %d"), wnum);
1733           DisplayError(buf, 0);
1734           v = VariantUnknown;
1735           break;
1736         }
1737       }
1738     }
1739     if (appData.debugMode) {
1740       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741               e, wnum, VariantName(v));
1742     }
1743     return v;
1744 }
1745
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1748
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750    advance *index beyond it, and set leftover_start to the new value of
1751    *index; else return FALSE.  If pattern contains the character '*', it
1752    matches any sequence of characters not containing '\r', '\n', or the
1753    character following the '*' (if any), and the matched sequence(s) are
1754    copied into star_match.
1755    */
1756 int
1757 looking_at(buf, index, pattern)
1758      char *buf;
1759      int *index;
1760      char *pattern;
1761 {
1762     char *bufp = &buf[*index], *patternp = pattern;
1763     int star_count = 0;
1764     char *matchp = star_match[0];
1765     
1766     for (;;) {
1767         if (*patternp == NULLCHAR) {
1768             *index = leftover_start = bufp - buf;
1769             *matchp = NULLCHAR;
1770             return TRUE;
1771         }
1772         if (*bufp == NULLCHAR) return FALSE;
1773         if (*patternp == '*') {
1774             if (*bufp == *(patternp + 1)) {
1775                 *matchp = NULLCHAR;
1776                 matchp = star_match[++star_count];
1777                 patternp += 2;
1778                 bufp++;
1779                 continue;
1780             } else if (*bufp == '\n' || *bufp == '\r') {
1781                 patternp++;
1782                 if (*patternp == NULLCHAR)
1783                   continue;
1784                 else
1785                   return FALSE;
1786             } else {
1787                 *matchp++ = *bufp++;
1788                 continue;
1789             }
1790         }
1791         if (*patternp != *bufp) return FALSE;
1792         patternp++;
1793         bufp++;
1794     }
1795 }
1796
1797 void
1798 SendToPlayer(data, length)
1799      char *data;
1800      int length;
1801 {
1802     int error, outCount;
1803     outCount = OutputToProcess(NoProc, data, length, &error);
1804     if (outCount < length) {
1805         DisplayFatalError(_("Error writing to display"), error, 1);
1806     }
1807 }
1808
1809 void
1810 PackHolding(packed, holding)
1811      char packed[];
1812      char *holding;
1813 {
1814     char *p = holding;
1815     char *q = packed;
1816     int runlength = 0;
1817     int curr = 9999;
1818     do {
1819         if (*p == curr) {
1820             runlength++;
1821         } else {
1822             switch (runlength) {
1823               case 0:
1824                 break;
1825               case 1:
1826                 *q++ = curr;
1827                 break;
1828               case 2:
1829                 *q++ = curr;
1830                 *q++ = curr;
1831                 break;
1832               default:
1833                 sprintf(q, "%d", runlength);
1834                 while (*q) q++;
1835                 *q++ = curr;
1836                 break;
1837             }
1838             runlength = 1;
1839             curr = *p;
1840         }
1841     } while (*p++);
1842     *q = NULLCHAR;
1843 }
1844
1845 /* Telnet protocol requests from the front end */
1846 void
1847 TelnetRequest(ddww, option)
1848      unsigned char ddww, option;
1849 {
1850     unsigned char msg[3];
1851     int outCount, outError;
1852
1853     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1854
1855     if (appData.debugMode) {
1856         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1857         switch (ddww) {
1858           case TN_DO:
1859             ddwwStr = "DO";
1860             break;
1861           case TN_DONT:
1862             ddwwStr = "DONT";
1863             break;
1864           case TN_WILL:
1865             ddwwStr = "WILL";
1866             break;
1867           case TN_WONT:
1868             ddwwStr = "WONT";
1869             break;
1870           default:
1871             ddwwStr = buf1;
1872             sprintf(buf1, "%d", ddww);
1873             break;
1874         }
1875         switch (option) {
1876           case TN_ECHO:
1877             optionStr = "ECHO";
1878             break;
1879           default:
1880             optionStr = buf2;
1881             sprintf(buf2, "%d", option);
1882             break;
1883         }
1884         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1885     }
1886     msg[0] = TN_IAC;
1887     msg[1] = ddww;
1888     msg[2] = option;
1889     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1890     if (outCount < 3) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895 void
1896 DoEcho()
1897 {
1898     if (!appData.icsActive) return;
1899     TelnetRequest(TN_DO, TN_ECHO);
1900 }
1901
1902 void
1903 DontEcho()
1904 {
1905     if (!appData.icsActive) return;
1906     TelnetRequest(TN_DONT, TN_ECHO);
1907 }
1908
1909 void
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1911 {
1912     /* put the holdings sent to us by the server on the board holdings area */
1913     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1914     char p;
1915     ChessSquare piece;
1916
1917     if(gameInfo.holdingsWidth < 2)  return;
1918     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919         return; // prevent overwriting by pre-board holdings
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948 }
1949
1950
1951 void
1952 VariantSwitch(Board board, VariantClass newVariant)
1953 {
1954    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1955    Board oldBoard;
1956
1957    startedFromPositionFile = FALSE;
1958    if(gameInfo.variant == newVariant) return;
1959
1960    /* [HGM] This routine is called each time an assignment is made to
1961     * gameInfo.variant during a game, to make sure the board sizes
1962     * are set to match the new variant. If that means adding or deleting
1963     * holdings, we shift the playing board accordingly
1964     * This kludge is needed because in ICS observe mode, we get boards
1965     * of an ongoing game without knowing the variant, and learn about the
1966     * latter only later. This can be because of the move list we requested,
1967     * in which case the game history is refilled from the beginning anyway,
1968     * but also when receiving holdings of a crazyhouse game. In the latter
1969     * case we want to add those holdings to the already received position.
1970     */
1971
1972    
1973    if (appData.debugMode) {
1974      fprintf(debugFP, "Switch board from %s to %s\n",
1975              VariantName(gameInfo.variant), VariantName(newVariant));
1976      setbuf(debugFP, NULL);
1977    }
1978    shuffleOpenings = 0;       /* [HGM] shuffle */
1979    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1980    switch(newVariant) 
1981      {
1982      case VariantShogi:
1983        newWidth = 9;  newHeight = 9;
1984        gameInfo.holdingsSize = 7;
1985      case VariantBughouse:
1986      case VariantCrazyhouse:
1987        newHoldingsWidth = 2; break;
1988      case VariantGreat:
1989        newWidth = 10;
1990      case VariantSuper:
1991        newHoldingsWidth = 2;
1992        gameInfo.holdingsSize = 8;
1993        break;
1994      case VariantGothic:
1995      case VariantCapablanca:
1996      case VariantCapaRandom:
1997        newWidth = 10;
1998      default:
1999        newHoldingsWidth = gameInfo.holdingsSize = 0;
2000      };
2001    
2002    if(newWidth  != gameInfo.boardWidth  ||
2003       newHeight != gameInfo.boardHeight ||
2004       newHoldingsWidth != gameInfo.holdingsWidth ) {
2005      
2006      /* shift position to new playing area, if needed */
2007      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008        for(i=0; i<BOARD_HEIGHT; i++) 
2009          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011              board[i][j];
2012        for(i=0; i<newHeight; i++) {
2013          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015        }
2016      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017        for(i=0; i<BOARD_HEIGHT; i++)
2018          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2020              board[i][j];
2021      }
2022      gameInfo.boardWidth  = newWidth;
2023      gameInfo.boardHeight = newHeight;
2024      gameInfo.holdingsWidth = newHoldingsWidth;
2025      gameInfo.variant = newVariant;
2026      InitDrawingSizes(-2, 0);
2027    } else gameInfo.variant = newVariant;
2028    CopyBoard(oldBoard, board);   // remember correctly formatted board
2029      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2030    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2031 }
2032
2033 static int loggedOn = FALSE;
2034
2035 /*-- Game start info cache: --*/
2036 int gs_gamenum;
2037 char gs_kind[MSG_SIZ];
2038 static char player1Name[128] = "";
2039 static char player2Name[128] = "";
2040 static char cont_seq[] = "\n\\   ";
2041 static int player1Rating = -1;
2042 static int player2Rating = -1;
2043 /*----------------------------*/
2044
2045 ColorClass curColor = ColorNormal;
2046 int suppressKibitz = 0;
2047
2048 void
2049 read_from_ics(isr, closure, data, count, error)
2050      InputSourceRef isr;
2051      VOIDSTAR closure;
2052      char *data;
2053      int count;
2054      int error;
2055 {
2056 #define BUF_SIZE 8192
2057 #define STARTED_NONE 0
2058 #define STARTED_MOVES 1
2059 #define STARTED_BOARD 2
2060 #define STARTED_OBSERVE 3
2061 #define STARTED_HOLDINGS 4
2062 #define STARTED_CHATTER 5
2063 #define STARTED_COMMENT 6
2064 #define STARTED_MOVES_NOHIDE 7
2065     
2066     static int started = STARTED_NONE;
2067     static char parse[20000];
2068     static int parse_pos = 0;
2069     static char buf[BUF_SIZE + 1];
2070     static int firstTime = TRUE, intfSet = FALSE;
2071     static ColorClass prevColor = ColorNormal;
2072     static int savingComment = FALSE;
2073     static int cmatch = 0; // continuation sequence match
2074     char *bp;
2075     char str[500];
2076     int i, oldi;
2077     int buf_len;
2078     int next_out;
2079     int tkind;
2080     int backup;    /* [DM] For zippy color lines */
2081     char *p;
2082     char talker[MSG_SIZ]; // [HGM] chat
2083     int channel;
2084
2085     if (appData.debugMode) {
2086       if (!error) {
2087         fprintf(debugFP, "<ICS: ");
2088         show_bytes(debugFP, data, count);
2089         fprintf(debugFP, "\n");
2090       }
2091     }
2092
2093     if (appData.debugMode) { int f = forwardMostMove;
2094         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2095                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2096                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106     /* copy new characters into the buffer */
2107     bp = buf + leftover_len;
2108     buf_len=leftover_len;
2109     for (i=0; i<count; i++)
2110     {
2111         // ignore these
2112         if (data[i] == '\r')
2113             continue;
2114
2115         // join lines split by ICS?
2116         if (!appData.noJoin)
2117         {
2118             /*
2119                 Joining just consists of finding matches against the
2120                 continuation sequence, and discarding that sequence
2121                 if found instead of copying it.  So, until a match
2122                 fails, there's nothing to do since it might be the
2123                 complete sequence, and thus, something we don't want
2124                 copied.
2125             */
2126             if (data[i] == cont_seq[cmatch])
2127             {
2128                 cmatch++;
2129                 if (cmatch == strlen(cont_seq))
2130                 {
2131                     cmatch = 0; // complete match.  just reset the counter
2132
2133                     /*
2134                         it's possible for the ICS to not include the space
2135                         at the end of the last word, making our [correct]
2136                         join operation fuse two separate words.  the server
2137                         does this when the space occurs at the width setting.
2138                     */
2139                     if (!buf_len || buf[buf_len-1] != ' ')
2140                     {
2141                         *bp++ = ' ';
2142                         buf_len++;
2143                     }
2144                 }
2145                 continue;
2146             }
2147             else if (cmatch)
2148             {
2149                 /*
2150                     match failed, so we have to copy what matched before
2151                     falling through and copying this character.  In reality,
2152                     this will only ever be just the newline character, but
2153                     it doesn't hurt to be precise.
2154                 */
2155                 strncpy(bp, cont_seq, cmatch);
2156                 bp += cmatch;
2157                 buf_len += cmatch;
2158                 cmatch = 0;
2159             }
2160         }
2161
2162         // copy this char
2163         *bp++ = data[i];
2164         buf_len++;
2165     }
2166
2167         buf[buf_len] = NULLCHAR;
2168         next_out = leftover_len;
2169         leftover_start = 0;
2170         
2171         i = 0;
2172         while (i < buf_len) {
2173             /* Deal with part of the TELNET option negotiation
2174                protocol.  We refuse to do anything beyond the
2175                defaults, except that we allow the WILL ECHO option,
2176                which ICS uses to turn off password echoing when we are
2177                directly connected to it.  We reject this option
2178                if localLineEditing mode is on (always on in xboard)
2179                and we are talking to port 23, which might be a real
2180                telnet server that will try to keep WILL ECHO on permanently.
2181              */
2182             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2183                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2184                 unsigned char option;
2185                 oldi = i;
2186                 switch ((unsigned char) buf[++i]) {
2187                   case TN_WILL:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<WILL ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       case TN_ECHO:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "ECHO ");
2194                         /* Reply only if this is a change, according
2195                            to the protocol rules. */
2196                         if (remoteEchoOption) break;
2197                         if (appData.localLineEditing &&
2198                             atoi(appData.icsPort) == TN_PORT) {
2199                             TelnetRequest(TN_DONT, TN_ECHO);
2200                         } else {
2201                             EchoOff();
2202                             TelnetRequest(TN_DO, TN_ECHO);
2203                             remoteEchoOption = TRUE;
2204                         }
2205                         break;
2206                       default:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "%d ", option);
2209                         /* Whatever this is, we don't want it. */
2210                         TelnetRequest(TN_DONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_WONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<WONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       case TN_ECHO:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "ECHO ");
2221                         /* Reply only if this is a change, according
2222                            to the protocol rules. */
2223                         if (!remoteEchoOption) break;
2224                         EchoOn();
2225                         TelnetRequest(TN_DONT, TN_ECHO);
2226                         remoteEchoOption = FALSE;
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", (unsigned char) option);
2231                         /* Whatever this is, it must already be turned
2232                            off, because we never agree to turn on
2233                            anything non-default, so according to the
2234                            protocol rules, we don't reply. */
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DO:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DO ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         /* Whatever this is, we refuse to do it. */
2244                         if (appData.debugMode)
2245                           fprintf(debugFP, "%d ", option);
2246                         TelnetRequest(TN_WONT, option);
2247                         break;
2248                     }
2249                     break;
2250                   case TN_DONT:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<DONT ");
2253                     switch (option = (unsigned char) buf[++i]) {
2254                       default:
2255                         if (appData.debugMode)
2256                           fprintf(debugFP, "%d ", option);
2257                         /* Whatever this is, we are already not doing
2258                            it, because we never agree to do anything
2259                            non-default, so according to the protocol
2260                            rules, we don't reply. */
2261                         break;
2262                     }
2263                     break;
2264                   case TN_IAC:
2265                     if (appData.debugMode)
2266                       fprintf(debugFP, "\n<IAC ");
2267                     /* Doubled IAC; pass it through */
2268                     i--;
2269                     break;
2270                   default:
2271                     if (appData.debugMode)
2272                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2273                     /* Drop all other telnet commands on the floor */
2274                     break;
2275                 }
2276                 if (oldi > next_out)
2277                   SendToPlayer(&buf[next_out], oldi - next_out);
2278                 if (++i > next_out)
2279                   next_out = i;
2280                 continue;
2281             }
2282                 
2283             /* OK, this at least will *usually* work */
2284             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2285                 loggedOn = TRUE;
2286             }
2287             
2288             if (loggedOn && !intfSet) {
2289                 if (ics_type == ICS_ICC) {
2290                   sprintf(str,
2291                           "/set-quietly interface %s\n/set-quietly style 12\n",
2292                           programVersion);
2293                 } else if (ics_type == ICS_CHESSNET) {
2294                   sprintf(str, "/style 12\n");
2295                 } else {
2296                   strcpy(str, "alias $ @\n$set interface ");
2297                   strcat(str, programVersion);
2298                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2299 #ifdef WIN32
2300                   strcat(str, "$iset nohighlight 1\n");
2301 #endif
2302                   strcat(str, "$iset lock 1\n$style 12\n");
2303                 }
2304                 SendToICS(str);
2305                 NotifyFrontendLogin();
2306                 intfSet = TRUE;
2307             }
2308
2309             if (started == STARTED_COMMENT) {
2310                 /* Accumulate characters in comment */
2311                 parse[parse_pos++] = buf[i];
2312                 if (buf[i] == '\n') {
2313                     parse[parse_pos] = NULLCHAR;
2314                     if(chattingPartner>=0) {
2315                         char mess[MSG_SIZ];
2316                         sprintf(mess, "%s%s", talker, parse);
2317                         OutputChatMessage(chattingPartner, mess);
2318                         chattingPartner = -1;
2319                     } else
2320                     if(!suppressKibitz) // [HGM] kibitz
2321                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2322                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2323                         int nrDigit = 0, nrAlph = 0, i;
2324                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2325                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2326                         parse[parse_pos] = NULLCHAR;
2327                         // try to be smart: if it does not look like search info, it should go to
2328                         // ICS interaction window after all, not to engine-output window.
2329                         for(i=0; i<parse_pos; i++) { // count letters and digits
2330                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2331                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2332                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2333                         }
2334                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2335                             int depth=0; float score;
2336                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2337                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2338                                 pvInfoList[forwardMostMove-1].depth = depth;
2339                                 pvInfoList[forwardMostMove-1].score = 100*score;
2340                             }
2341                             OutputKibitz(suppressKibitz, parse);
2342                         } else {
2343                             char tmp[MSG_SIZ];
2344                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2345                             SendToPlayer(tmp, strlen(tmp));
2346                         }
2347                     }
2348                     started = STARTED_NONE;
2349                 } else {
2350                     /* Don't match patterns against characters in chatter */
2351                     i++;
2352                     continue;
2353                 }
2354             }
2355             if (started == STARTED_CHATTER) {
2356                 if (buf[i] != '\n') {
2357                     /* Don't match patterns against characters in chatter */
2358                     i++;
2359                     continue;
2360                 }
2361                 started = STARTED_NONE;
2362             }
2363
2364             /* Kludge to deal with rcmd protocol */
2365             if (firstTime && looking_at(buf, &i, "\001*")) {
2366                 DisplayFatalError(&buf[1], 0, 1);
2367                 continue;
2368             } else {
2369                 firstTime = FALSE;
2370             }
2371
2372             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2373                 ics_type = ICS_ICC;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2380                 ics_type = ICS_FICS;
2381                 ics_prefix = "$";
2382                 if (appData.debugMode)
2383                   fprintf(debugFP, "ics_type %d\n", ics_type);
2384                 continue;
2385             }
2386             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2387                 ics_type = ICS_CHESSNET;
2388                 ics_prefix = "/";
2389                 if (appData.debugMode)
2390                   fprintf(debugFP, "ics_type %d\n", ics_type);
2391                 continue;
2392             }
2393
2394             if (!loggedOn &&
2395                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2396                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2397                  looking_at(buf, &i, "will be \"*\""))) {
2398               strcpy(ics_handle, star_match[0]);
2399               continue;
2400             }
2401
2402             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2403               char buf[MSG_SIZ];
2404               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2405               DisplayIcsInteractionTitle(buf);
2406               have_set_title = TRUE;
2407             }
2408
2409             /* skip finger notes */
2410             if (started == STARTED_NONE &&
2411                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2412                  (buf[i] == '1' && buf[i+1] == '0')) &&
2413                 buf[i+2] == ':' && buf[i+3] == ' ') {
2414               started = STARTED_CHATTER;
2415               i += 3;
2416               continue;
2417             }
2418
2419             /* skip formula vars */
2420             if (started == STARTED_NONE &&
2421                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2422               started = STARTED_CHATTER;
2423               i += 3;
2424               continue;
2425             }
2426
2427             oldi = i;
2428             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2429             if (appData.autoKibitz && started == STARTED_NONE && 
2430                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2431                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2432                 if(looking_at(buf, &i, "* kibitzes: ") &&
2433                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2434                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2435                         suppressKibitz = TRUE;
2436                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2437                                 && (gameMode == IcsPlayingWhite)) ||
2438                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2439                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2440                             started = STARTED_CHATTER; // own kibitz we simply discard
2441                         else {
2442                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2443                             parse_pos = 0; parse[0] = NULLCHAR;
2444                             savingComment = TRUE;
2445                             suppressKibitz = gameMode != IcsObserving ? 2 :
2446                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2447                         } 
2448                         continue;
2449                 } else
2450                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2451                     started = STARTED_CHATTER;
2452                     suppressKibitz = TRUE;
2453                 }
2454             } // [HGM] kibitz: end of patch
2455
2456 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2457
2458             // [HGM] chat: intercept tells by users for which we have an open chat window
2459             channel = -1;
2460             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2461                                            looking_at(buf, &i, "* whispers:") ||
2462                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2463                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2464                 int p;
2465                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2466                 chattingPartner = -1;
2467
2468                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2469                 for(p=0; p<MAX_CHAT; p++) {
2470                     if(channel == atoi(chatPartner[p])) {
2471                     talker[0] = '['; strcat(talker, "]");
2472                     chattingPartner = p; break;
2473                     }
2474                 } else
2475                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2476                 for(p=0; p<MAX_CHAT; p++) {
2477                     if(!strcmp("WHISPER", chatPartner[p])) {
2478                         talker[0] = '['; strcat(talker, "]");
2479                         chattingPartner = p; break;
2480                     }
2481                 }
2482                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2483                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2484                     talker[0] = 0;
2485                     chattingPartner = p; break;
2486                 }
2487                 if(chattingPartner<0) i = oldi; else {
2488                     started = STARTED_COMMENT;
2489                     parse_pos = 0; parse[0] = NULLCHAR;
2490                     savingComment = TRUE;
2491                     suppressKibitz = TRUE;
2492                 }
2493             } // [HGM] chat: end of patch
2494
2495             if (appData.zippyTalk || appData.zippyPlay) {
2496                 /* [DM] Backup address for color zippy lines */
2497                 backup = i;
2498 #if ZIPPY
2499        #ifdef WIN32
2500                if (loggedOn == TRUE)
2501                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2502                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2503        #else
2504                 if (ZippyControl(buf, &i) ||
2505                     ZippyConverse(buf, &i) ||
2506                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2507                       loggedOn = TRUE;
2508                       if (!appData.colorize) continue;
2509                 }
2510        #endif
2511 #endif
2512             } // [DM] 'else { ' deleted
2513                 if (
2514                     /* Regular tells and says */
2515                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2516                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2517                     looking_at(buf, &i, "* says: ") ||
2518                     /* Don't color "message" or "messages" output */
2519                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2520                     looking_at(buf, &i, "*. * at *:*: ") ||
2521                     looking_at(buf, &i, "--* (*:*): ") ||
2522                     /* Message notifications (same color as tells) */
2523                     looking_at(buf, &i, "* has left a message ") ||
2524                     looking_at(buf, &i, "* just sent you a message:\n") ||
2525                     /* Whispers and kibitzes */
2526                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2527                     looking_at(buf, &i, "* kibitzes: ") ||
2528                     /* Channel tells */
2529                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2530
2531                   if (tkind == 1 && strchr(star_match[0], ':')) {
2532                       /* Avoid "tells you:" spoofs in channels */
2533                      tkind = 3;
2534                   }
2535                   if (star_match[0][0] == NULLCHAR ||
2536                       strchr(star_match[0], ' ') ||
2537                       (tkind == 3 && strchr(star_match[1], ' '))) {
2538                     /* Reject bogus matches */
2539                     i = oldi;
2540                   } else {
2541                     if (appData.colorize) {
2542                       if (oldi > next_out) {
2543                         SendToPlayer(&buf[next_out], oldi - next_out);
2544                         next_out = oldi;
2545                       }
2546                       switch (tkind) {
2547                       case 1:
2548                         Colorize(ColorTell, FALSE);
2549                         curColor = ColorTell;
2550                         break;
2551                       case 2:
2552                         Colorize(ColorKibitz, FALSE);
2553                         curColor = ColorKibitz;
2554                         break;
2555                       case 3:
2556                         p = strrchr(star_match[1], '(');
2557                         if (p == NULL) {
2558                           p = star_match[1];
2559                         } else {
2560                           p++;
2561                         }
2562                         if (atoi(p) == 1) {
2563                           Colorize(ColorChannel1, FALSE);
2564                           curColor = ColorChannel1;
2565                         } else {
2566                           Colorize(ColorChannel, FALSE);
2567                           curColor = ColorChannel;
2568                         }
2569                         break;
2570                       case 5:
2571                         curColor = ColorNormal;
2572                         break;
2573                       }
2574                     }
2575                     if (started == STARTED_NONE && appData.autoComment &&
2576                         (gameMode == IcsObserving ||
2577                          gameMode == IcsPlayingWhite ||
2578                          gameMode == IcsPlayingBlack)) {
2579                       parse_pos = i - oldi;
2580                       memcpy(parse, &buf[oldi], parse_pos);
2581                       parse[parse_pos] = NULLCHAR;
2582                       started = STARTED_COMMENT;
2583                       savingComment = TRUE;
2584                     } else {
2585                       started = STARTED_CHATTER;
2586                       savingComment = FALSE;
2587                     }
2588                     loggedOn = TRUE;
2589                     continue;
2590                   }
2591                 }
2592
2593                 if (looking_at(buf, &i, "* s-shouts: ") ||
2594                     looking_at(buf, &i, "* c-shouts: ")) {
2595                     if (appData.colorize) {
2596                         if (oldi > next_out) {
2597                             SendToPlayer(&buf[next_out], oldi - next_out);
2598                             next_out = oldi;
2599                         }
2600                         Colorize(ColorSShout, FALSE);
2601                         curColor = ColorSShout;
2602                     }
2603                     loggedOn = TRUE;
2604                     started = STARTED_CHATTER;
2605                     continue;
2606                 }
2607
2608                 if (looking_at(buf, &i, "--->")) {
2609                     loggedOn = TRUE;
2610                     continue;
2611                 }
2612
2613                 if (looking_at(buf, &i, "* shouts: ") ||
2614                     looking_at(buf, &i, "--> ")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorShout, FALSE);
2621                         curColor = ColorShout;
2622                     }
2623                     loggedOn = TRUE;
2624                     started = STARTED_CHATTER;
2625                     continue;
2626                 }
2627
2628                 if (looking_at( buf, &i, "Challenge:")) {
2629                     if (appData.colorize) {
2630                         if (oldi > next_out) {
2631                             SendToPlayer(&buf[next_out], oldi - next_out);
2632                             next_out = oldi;
2633                         }
2634                         Colorize(ColorChallenge, FALSE);
2635                         curColor = ColorChallenge;
2636                     }
2637                     loggedOn = TRUE;
2638                     continue;
2639                 }
2640
2641                 if (looking_at(buf, &i, "* offers you") ||
2642                     looking_at(buf, &i, "* offers to be") ||
2643                     looking_at(buf, &i, "* would like to") ||
2644                     looking_at(buf, &i, "* requests to") ||
2645                     looking_at(buf, &i, "Your opponent offers") ||
2646                     looking_at(buf, &i, "Your opponent requests")) {
2647
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorRequest, FALSE);
2654                         curColor = ColorRequest;
2655                     }
2656                     continue;
2657                 }
2658
2659                 if (looking_at(buf, &i, "* (*) seeking")) {
2660                     if (appData.colorize) {
2661                         if (oldi > next_out) {
2662                             SendToPlayer(&buf[next_out], oldi - next_out);
2663                             next_out = oldi;
2664                         }
2665                         Colorize(ColorSeek, FALSE);
2666                         curColor = ColorSeek;
2667                     }
2668                     continue;
2669             }
2670
2671             if (looking_at(buf, &i, "\\   ")) {
2672                 if (prevColor != ColorNormal) {
2673                     if (oldi > next_out) {
2674                         SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = oldi;
2676                     }
2677                     Colorize(prevColor, TRUE);
2678                     curColor = prevColor;
2679                 }
2680                 if (savingComment) {
2681                     parse_pos = i - oldi;
2682                     memcpy(parse, &buf[oldi], parse_pos);
2683                     parse[parse_pos] = NULLCHAR;
2684                     started = STARTED_COMMENT;
2685                 } else {
2686                     started = STARTED_CHATTER;
2687                 }
2688                 continue;
2689             }
2690
2691             if (looking_at(buf, &i, "Black Strength :") ||
2692                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2693                 looking_at(buf, &i, "<10>") ||
2694                 looking_at(buf, &i, "#@#")) {
2695                 /* Wrong board style */
2696                 loggedOn = TRUE;
2697                 SendToICS(ics_prefix);
2698                 SendToICS("set style 12\n");
2699                 SendToICS(ics_prefix);
2700                 SendToICS("refresh\n");
2701                 continue;
2702             }
2703             
2704             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2705                 ICSInitScript();
2706                 have_sent_ICS_logon = 1;
2707                 continue;
2708             }
2709               
2710             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2711                 (looking_at(buf, &i, "\n<12> ") ||
2712                  looking_at(buf, &i, "<12> "))) {
2713                 loggedOn = TRUE;
2714                 if (oldi > next_out) {
2715                     SendToPlayer(&buf[next_out], oldi - next_out);
2716                 }
2717                 next_out = i;
2718                 started = STARTED_BOARD;
2719                 parse_pos = 0;
2720                 continue;
2721             }
2722
2723             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2724                 looking_at(buf, &i, "<b1> ")) {
2725                 if (oldi > next_out) {
2726                     SendToPlayer(&buf[next_out], oldi - next_out);
2727                 }
2728                 next_out = i;
2729                 started = STARTED_HOLDINGS;
2730                 parse_pos = 0;
2731                 continue;
2732             }
2733
2734             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2735                 loggedOn = TRUE;
2736                 /* Header for a move list -- first line */
2737
2738                 switch (ics_getting_history) {
2739                   case H_FALSE:
2740                     switch (gameMode) {
2741                       case IcsIdle:
2742                       case BeginningOfGame:
2743                         /* User typed "moves" or "oldmoves" while we
2744                            were idle.  Pretend we asked for these
2745                            moves and soak them up so user can step
2746                            through them and/or save them.
2747                            */
2748                         Reset(FALSE, TRUE);
2749                         gameMode = IcsObserving;
2750                         ModeHighlight();
2751                         ics_gamenum = -1;
2752                         ics_getting_history = H_GOT_UNREQ_HEADER;
2753                         break;
2754                       case EditGame: /*?*/
2755                       case EditPosition: /*?*/
2756                         /* Should above feature work in these modes too? */
2757                         /* For now it doesn't */
2758                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2759                         break;
2760                       default:
2761                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2762                         break;
2763                     }
2764                     break;
2765                   case H_REQUESTED:
2766                     /* Is this the right one? */
2767                     if (gameInfo.white && gameInfo.black &&
2768                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2769                         strcmp(gameInfo.black, star_match[2]) == 0) {
2770                         /* All is well */
2771                         ics_getting_history = H_GOT_REQ_HEADER;
2772                     }
2773                     break;
2774                   case H_GOT_REQ_HEADER:
2775                   case H_GOT_UNREQ_HEADER:
2776                   case H_GOT_UNWANTED_HEADER:
2777                   case H_GETTING_MOVES:
2778                     /* Should not happen */
2779                     DisplayError(_("Error gathering move list: two headers"), 0);
2780                     ics_getting_history = H_FALSE;
2781                     break;
2782                 }
2783
2784                 /* Save player ratings into gameInfo if needed */
2785                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2786                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2787                     (gameInfo.whiteRating == -1 ||
2788                      gameInfo.blackRating == -1)) {
2789
2790                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2791                     gameInfo.blackRating = string_to_rating(star_match[3]);
2792                     if (appData.debugMode)
2793                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2794                               gameInfo.whiteRating, gameInfo.blackRating);
2795                 }
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i,
2800               "* * match, initial time: * minute*, increment: * second")) {
2801                 /* Header for a move list -- second line */
2802                 /* Initial board will follow if this is a wild game */
2803                 if (gameInfo.event != NULL) free(gameInfo.event);
2804                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2805                 gameInfo.event = StrSave(str);
2806                 /* [HGM] we switched variant. Translate boards if needed. */
2807                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2808                 continue;
2809             }
2810
2811             if (looking_at(buf, &i, "Move  ")) {
2812                 /* Beginning of a move list */
2813                 switch (ics_getting_history) {
2814                   case H_FALSE:
2815                     /* Normally should not happen */
2816                     /* Maybe user hit reset while we were parsing */
2817                     break;
2818                   case H_REQUESTED:
2819                     /* Happens if we are ignoring a move list that is not
2820                      * the one we just requested.  Common if the user
2821                      * tries to observe two games without turning off
2822                      * getMoveList */
2823                     break;
2824                   case H_GETTING_MOVES:
2825                     /* Should not happen */
2826                     DisplayError(_("Error gathering move list: nested"), 0);
2827                     ics_getting_history = H_FALSE;
2828                     break;
2829                   case H_GOT_REQ_HEADER:
2830                     ics_getting_history = H_GETTING_MOVES;
2831                     started = STARTED_MOVES;
2832                     parse_pos = 0;
2833                     if (oldi > next_out) {
2834                         SendToPlayer(&buf[next_out], oldi - next_out);
2835                     }
2836                     break;
2837                   case H_GOT_UNREQ_HEADER:
2838                     ics_getting_history = H_GETTING_MOVES;
2839                     started = STARTED_MOVES_NOHIDE;
2840                     parse_pos = 0;
2841                     break;
2842                   case H_GOT_UNWANTED_HEADER:
2843                     ics_getting_history = H_FALSE;
2844                     break;
2845                 }
2846                 continue;
2847             }                           
2848             
2849             if (looking_at(buf, &i, "% ") ||
2850                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2851                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2852                 savingComment = FALSE;
2853                 switch (started) {
2854                   case STARTED_MOVES:
2855                   case STARTED_MOVES_NOHIDE:
2856                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2857                     parse[parse_pos + i - oldi] = NULLCHAR;
2858                     ParseGameHistory(parse);
2859 #if ZIPPY
2860                     if (appData.zippyPlay && first.initDone) {
2861                         FeedMovesToProgram(&first, forwardMostMove);
2862                         if (gameMode == IcsPlayingWhite) {
2863                             if (WhiteOnMove(forwardMostMove)) {
2864                                 if (first.sendTime) {
2865                                   if (first.useColors) {
2866                                     SendToProgram("black\n", &first); 
2867                                   }
2868                                   SendTimeRemaining(&first, TRUE);
2869                                 }
2870                                 if (first.useColors) {
2871                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2872                                 }
2873                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2874                                 first.maybeThinking = TRUE;
2875                             } else {
2876                                 if (first.usePlayother) {
2877                                   if (first.sendTime) {
2878                                     SendTimeRemaining(&first, TRUE);
2879                                   }
2880                                   SendToProgram("playother\n", &first);
2881                                   firstMove = FALSE;
2882                                 } else {
2883                                   firstMove = TRUE;
2884                                 }
2885                             }
2886                         } else if (gameMode == IcsPlayingBlack) {
2887                             if (!WhiteOnMove(forwardMostMove)) {
2888                                 if (first.sendTime) {
2889                                   if (first.useColors) {
2890                                     SendToProgram("white\n", &first);
2891                                   }
2892                                   SendTimeRemaining(&first, FALSE);
2893                                 }
2894                                 if (first.useColors) {
2895                                   SendToProgram("black\n", &first);
2896                                 }
2897                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2898                                 first.maybeThinking = TRUE;
2899                             } else {
2900                                 if (first.usePlayother) {
2901                                   if (first.sendTime) {
2902                                     SendTimeRemaining(&first, FALSE);
2903                                   }
2904                                   SendToProgram("playother\n", &first);
2905                                   firstMove = FALSE;
2906                                 } else {
2907                                   firstMove = TRUE;
2908                                 }
2909                             }
2910                         }                       
2911                     }
2912 #endif
2913                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2914                         /* Moves came from oldmoves or moves command
2915                            while we weren't doing anything else.
2916                            */
2917                         currentMove = forwardMostMove;
2918                         ClearHighlights();/*!!could figure this out*/
2919                         flipView = appData.flipView;
2920                         DrawPosition(TRUE, boards[currentMove]);
2921                         DisplayBothClocks();
2922                         sprintf(str, "%s vs. %s",
2923                                 gameInfo.white, gameInfo.black);
2924                         DisplayTitle(str);
2925                         gameMode = IcsIdle;
2926                     } else {
2927                         /* Moves were history of an active game */
2928                         if (gameInfo.resultDetails != NULL) {
2929                             free(gameInfo.resultDetails);
2930                             gameInfo.resultDetails = NULL;
2931                         }
2932                     }
2933                     HistorySet(parseList, backwardMostMove,
2934                                forwardMostMove, currentMove-1);
2935                     DisplayMove(currentMove - 1);
2936                     if (started == STARTED_MOVES) next_out = i;
2937                     started = STARTED_NONE;
2938                     ics_getting_history = H_FALSE;
2939                     break;
2940
2941                   case STARTED_OBSERVE:
2942                     started = STARTED_NONE;
2943                     SendToICS(ics_prefix);
2944                     SendToICS("refresh\n");
2945                     break;
2946
2947                   default:
2948                     break;
2949                 }
2950                 if(bookHit) { // [HGM] book: simulate book reply
2951                     static char bookMove[MSG_SIZ]; // a bit generous?
2952
2953                     programStats.nodes = programStats.depth = programStats.time = 
2954                     programStats.score = programStats.got_only_move = 0;
2955                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2956
2957                     strcpy(bookMove, "move ");
2958                     strcat(bookMove, bookHit);
2959                     HandleMachineMove(bookMove, &first);
2960                 }
2961                 continue;
2962             }
2963             
2964             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2965                  started == STARTED_HOLDINGS ||
2966                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2967                 /* Accumulate characters in move list or board */
2968                 parse[parse_pos++] = buf[i];
2969             }
2970             
2971             /* Start of game messages.  Mostly we detect start of game
2972                when the first board image arrives.  On some versions
2973                of the ICS, though, we need to do a "refresh" after starting
2974                to observe in order to get the current board right away. */
2975             if (looking_at(buf, &i, "Adding game * to observation list")) {
2976                 started = STARTED_OBSERVE;
2977                 continue;
2978             }
2979
2980             /* Handle auto-observe */
2981             if (appData.autoObserve &&
2982                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2983                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2984                 char *player;
2985                 /* Choose the player that was highlighted, if any. */
2986                 if (star_match[0][0] == '\033' ||
2987                     star_match[1][0] != '\033') {
2988                     player = star_match[0];
2989                 } else {
2990                     player = star_match[2];
2991                 }
2992                 sprintf(str, "%sobserve %s\n",
2993                         ics_prefix, StripHighlightAndTitle(player));
2994                 SendToICS(str);
2995
2996                 /* Save ratings from notify string */
2997                 strcpy(player1Name, star_match[0]);
2998                 player1Rating = string_to_rating(star_match[1]);
2999                 strcpy(player2Name, star_match[2]);
3000                 player2Rating = string_to_rating(star_match[3]);
3001
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, 
3004                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3005                           player1Name, player1Rating,
3006                           player2Name, player2Rating);
3007
3008                 continue;
3009             }
3010
3011             /* Deal with automatic examine mode after a game,
3012                and with IcsObserving -> IcsExamining transition */
3013             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3014                 looking_at(buf, &i, "has made you an examiner of game *")) {
3015
3016                 int gamenum = atoi(star_match[0]);
3017                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3018                     gamenum == ics_gamenum) {
3019                     /* We were already playing or observing this game;
3020                        no need to refetch history */
3021                     gameMode = IcsExamining;
3022                     if (pausing) {
3023                         pauseExamForwardMostMove = forwardMostMove;
3024                     } else if (currentMove < forwardMostMove) {
3025                         ForwardInner(forwardMostMove);
3026                     }
3027                 } else {
3028                     /* I don't think this case really can happen */
3029                     SendToICS(ics_prefix);
3030                     SendToICS("refresh\n");
3031                 }
3032                 continue;
3033             }    
3034             
3035             /* Error messages */
3036 //          if (ics_user_moved) {
3037             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3038                 if (looking_at(buf, &i, "Illegal move") ||
3039                     looking_at(buf, &i, "Not a legal move") ||
3040                     looking_at(buf, &i, "Your king is in check") ||
3041                     looking_at(buf, &i, "It isn't your turn") ||
3042                     looking_at(buf, &i, "It is not your move")) {
3043                     /* Illegal move */
3044                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3045                         currentMove = --forwardMostMove;
3046                         DisplayMove(currentMove - 1); /* before DMError */
3047                         DrawPosition(FALSE, boards[currentMove]);
3048                         SwitchClocks();
3049                         DisplayBothClocks();
3050                     }
3051                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3052                     ics_user_moved = 0;
3053                     continue;
3054                 }
3055             }
3056
3057             if (looking_at(buf, &i, "still have time") ||
3058                 looking_at(buf, &i, "not out of time") ||
3059                 looking_at(buf, &i, "either player is out of time") ||
3060                 looking_at(buf, &i, "has timeseal; checking")) {
3061                 /* We must have called his flag a little too soon */
3062                 whiteFlag = blackFlag = FALSE;
3063                 continue;
3064             }
3065
3066             if (looking_at(buf, &i, "added * seconds to") ||
3067                 looking_at(buf, &i, "seconds were added to")) {
3068                 /* Update the clocks */
3069                 SendToICS(ics_prefix);
3070                 SendToICS("refresh\n");
3071                 continue;
3072             }
3073
3074             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3075                 ics_clock_paused = TRUE;
3076                 StopClocks();
3077                 continue;
3078             }
3079
3080             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3081                 ics_clock_paused = FALSE;
3082                 StartClocks();
3083                 continue;
3084             }
3085
3086             /* Grab player ratings from the Creating: message.
3087                Note we have to check for the special case when
3088                the ICS inserts things like [white] or [black]. */
3089             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3090                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3091                 /* star_matches:
3092                    0    player 1 name (not necessarily white)
3093                    1    player 1 rating
3094                    2    empty, white, or black (IGNORED)
3095                    3    player 2 name (not necessarily black)
3096                    4    player 2 rating
3097                    
3098                    The names/ratings are sorted out when the game
3099                    actually starts (below).
3100                 */
3101                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3102                 player1Rating = string_to_rating(star_match[1]);
3103                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3104                 player2Rating = string_to_rating(star_match[4]);
3105
3106                 if (appData.debugMode)
3107                   fprintf(debugFP, 
3108                           "Ratings from 'Creating:' %s %d, %s %d\n",
3109                           player1Name, player1Rating,
3110                           player2Name, player2Rating);
3111
3112                 continue;
3113             }
3114             
3115             /* Improved generic start/end-of-game messages */
3116             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3117                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3118                 /* If tkind == 0: */
3119                 /* star_match[0] is the game number */
3120                 /*           [1] is the white player's name */
3121                 /*           [2] is the black player's name */
3122                 /* For end-of-game: */
3123                 /*           [3] is the reason for the game end */
3124                 /*           [4] is a PGN end game-token, preceded by " " */
3125                 /* For start-of-game: */
3126                 /*           [3] begins with "Creating" or "Continuing" */
3127                 /*           [4] is " *" or empty (don't care). */
3128                 int gamenum = atoi(star_match[0]);
3129                 char *whitename, *blackname, *why, *endtoken;
3130                 ChessMove endtype = (ChessMove) 0;
3131
3132                 if (tkind == 0) {
3133                   whitename = star_match[1];
3134                   blackname = star_match[2];
3135                   why = star_match[3];
3136                   endtoken = star_match[4];
3137                 } else {
3138                   whitename = star_match[1];
3139                   blackname = star_match[3];
3140                   why = star_match[5];
3141                   endtoken = star_match[6];
3142                 }
3143
3144                 /* Game start messages */
3145                 if (strncmp(why, "Creating ", 9) == 0 ||
3146                     strncmp(why, "Continuing ", 11) == 0) {
3147                     gs_gamenum = gamenum;
3148                     strcpy(gs_kind, strchr(why, ' ') + 1);
3149 #if ZIPPY
3150                     if (appData.zippyPlay) {
3151                         ZippyGameStart(whitename, blackname);
3152                     }
3153 #endif /*ZIPPY*/
3154                     continue;
3155                 }
3156
3157                 /* Game end messages */
3158                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3159                     ics_gamenum != gamenum) {
3160                     continue;
3161                 }
3162                 while (endtoken[0] == ' ') endtoken++;
3163                 switch (endtoken[0]) {
3164                   case '*':
3165                   default:
3166                     endtype = GameUnfinished;
3167                     break;
3168                   case '0':
3169                     endtype = BlackWins;
3170                     break;
3171                   case '1':
3172                     if (endtoken[1] == '/')
3173                       endtype = GameIsDrawn;
3174                     else
3175                       endtype = WhiteWins;
3176                     break;
3177                 }
3178                 GameEnds(endtype, why, GE_ICS);
3179 #if ZIPPY
3180                 if (appData.zippyPlay && first.initDone) {
3181                     ZippyGameEnd(endtype, why);
3182                     if (first.pr == NULL) {
3183                       /* Start the next process early so that we'll
3184                          be ready for the next challenge */
3185                       StartChessProgram(&first);
3186                     }
3187                     /* Send "new" early, in case this command takes
3188                        a long time to finish, so that we'll be ready
3189                        for the next challenge. */
3190                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3191                     Reset(TRUE, TRUE);
3192                 }
3193 #endif /*ZIPPY*/
3194                 continue;
3195             }
3196
3197             if (looking_at(buf, &i, "Removing game * from observation") ||
3198                 looking_at(buf, &i, "no longer observing game *") ||
3199                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3200                 if (gameMode == IcsObserving &&
3201                     atoi(star_match[0]) == ics_gamenum)
3202                   {
3203                       /* icsEngineAnalyze */
3204                       if (appData.icsEngineAnalyze) {
3205                             ExitAnalyzeMode();
3206                             ModeHighlight();
3207                       }
3208                       StopClocks();
3209                       gameMode = IcsIdle;
3210                       ics_gamenum = -1;
3211                       ics_user_moved = FALSE;
3212                   }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i, "no longer examining game *")) {
3217                 if (gameMode == IcsExamining &&
3218                     atoi(star_match[0]) == ics_gamenum)
3219                   {
3220                       gameMode = IcsIdle;
3221                       ics_gamenum = -1;
3222                       ics_user_moved = FALSE;
3223                   }
3224                 continue;
3225             }
3226
3227             /* Advance leftover_start past any newlines we find,
3228                so only partial lines can get reparsed */
3229             if (looking_at(buf, &i, "\n")) {
3230                 prevColor = curColor;
3231                 if (curColor != ColorNormal) {
3232                     if (oldi > next_out) {
3233                         SendToPlayer(&buf[next_out], oldi - next_out);
3234                         next_out = oldi;
3235                     }
3236                     Colorize(ColorNormal, FALSE);
3237                     curColor = ColorNormal;
3238                 }
3239                 if (started == STARTED_BOARD) {
3240                     started = STARTED_NONE;
3241                     parse[parse_pos] = NULLCHAR;
3242                     ParseBoard12(parse);
3243                     ics_user_moved = 0;
3244
3245                     /* Send premove here */
3246                     if (appData.premove) {
3247                       char str[MSG_SIZ];
3248                       if (currentMove == 0 &&
3249                           gameMode == IcsPlayingWhite &&
3250                           appData.premoveWhite) {
3251                         sprintf(str, "%s\n", appData.premoveWhiteText);
3252                         if (appData.debugMode)
3253                           fprintf(debugFP, "Sending premove:\n");
3254                         SendToICS(str);
3255                       } else if (currentMove == 1 &&
3256                                  gameMode == IcsPlayingBlack &&
3257                                  appData.premoveBlack) {
3258                         sprintf(str, "%s\n", appData.premoveBlackText);
3259                         if (appData.debugMode)
3260                           fprintf(debugFP, "Sending premove:\n");
3261                         SendToICS(str);
3262                       } else if (gotPremove) {
3263                         gotPremove = 0;
3264                         ClearPremoveHighlights();
3265                         if (appData.debugMode)
3266                           fprintf(debugFP, "Sending premove:\n");
3267                           UserMoveEvent(premoveFromX, premoveFromY, 
3268                                         premoveToX, premoveToY, 
3269                                         premovePromoChar);
3270                       }
3271                     }
3272
3273                     /* Usually suppress following prompt */
3274                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3275                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3276                         if (looking_at(buf, &i, "*% ")) {
3277                             savingComment = FALSE;
3278                         }
3279                     }
3280                     next_out = i;
3281                 } else if (started == STARTED_HOLDINGS) {
3282                     int gamenum;
3283                     char new_piece[MSG_SIZ];
3284                     started = STARTED_NONE;
3285                     parse[parse_pos] = NULLCHAR;
3286                     if (appData.debugMode)
3287                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3288                                                         parse, currentMove);
3289                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3290                         gamenum == ics_gamenum) {
3291                         if (gameInfo.variant == VariantNormal) {
3292                           /* [HGM] We seem to switch variant during a game!
3293                            * Presumably no holdings were displayed, so we have
3294                            * to move the position two files to the right to
3295                            * create room for them!
3296                            */
3297                           VariantClass newVariant;
3298                           switch(gameInfo.boardWidth) { // base guess on board width
3299                                 case 9:  newVariant = VariantShogi; break;
3300                                 case 10: newVariant = VariantGreat; break;
3301                                 default: newVariant = VariantCrazyhouse; break;
3302                           }
3303                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3304                           /* Get a move list just to see the header, which
3305                              will tell us whether this is really bug or zh */
3306                           if (ics_getting_history == H_FALSE) {
3307                             ics_getting_history = H_REQUESTED;
3308                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3309                             SendToICS(str);
3310                           }
3311                         }
3312                         new_piece[0] = NULLCHAR;
3313                         sscanf(parse, "game %d white [%s black [%s <- %s",
3314                                &gamenum, white_holding, black_holding,
3315                                new_piece);
3316                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3317                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3318                         /* [HGM] copy holdings to board holdings area */
3319                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3320                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3321                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3322 #if ZIPPY
3323                         if (appData.zippyPlay && first.initDone) {
3324                             ZippyHoldings(white_holding, black_holding,
3325                                           new_piece);
3326                         }
3327 #endif /*ZIPPY*/
3328                         if (tinyLayout || smallLayout) {
3329                             char wh[16], bh[16];
3330                             PackHolding(wh, white_holding);
3331                             PackHolding(bh, black_holding);
3332                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3333                                     gameInfo.white, gameInfo.black);
3334                         } else {
3335                             sprintf(str, "%s [%s] vs. %s [%s]",
3336                                     gameInfo.white, white_holding,
3337                                     gameInfo.black, black_holding);
3338                         }
3339
3340                         DrawPosition(FALSE, boards[currentMove]);
3341                         DisplayTitle(str);
3342                     }
3343                     /* Suppress following prompt */
3344                     if (looking_at(buf, &i, "*% ")) {
3345                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3346                         savingComment = FALSE;
3347                     }
3348                     next_out = i;
3349                 }
3350                 continue;
3351             }
3352
3353             i++;                /* skip unparsed character and loop back */
3354         }
3355         
3356         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3357             started != STARTED_HOLDINGS && i > next_out) {
3358             SendToPlayer(&buf[next_out], i - next_out);
3359             next_out = i;
3360         }
3361         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3362         
3363         leftover_len = buf_len - leftover_start;
3364         /* if buffer ends with something we couldn't parse,
3365            reparse it after appending the next read */
3366         
3367     } else if (count == 0) {
3368         RemoveInputSource(isr);
3369         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3370     } else {
3371         DisplayFatalError(_("Error reading from ICS"), error, 1);
3372     }
3373 }
3374
3375
3376 /* Board style 12 looks like this:
3377    
3378    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3379    
3380  * The "<12> " is stripped before it gets to this routine.  The two
3381  * trailing 0's (flip state and clock ticking) are later addition, and
3382  * some chess servers may not have them, or may have only the first.
3383  * Additional trailing fields may be added in the future.  
3384  */
3385
3386 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3387
3388 #define RELATION_OBSERVING_PLAYED    0
3389 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3390 #define RELATION_PLAYING_MYMOVE      1
3391 #define RELATION_PLAYING_NOTMYMOVE  -1
3392 #define RELATION_EXAMINING           2
3393 #define RELATION_ISOLATED_BOARD     -3
3394 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3395
3396 void
3397 ParseBoard12(string)
3398      char *string;
3399
3400     GameMode newGameMode;
3401     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3402     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3403     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3404     char to_play, board_chars[200];
3405     char move_str[500], str[500], elapsed_time[500];
3406     char black[32], white[32];
3407     Board board;
3408     int prevMove = currentMove;
3409     int ticking = 2;
3410     ChessMove moveType;
3411     int fromX, fromY, toX, toY;
3412     char promoChar;
3413     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3414     char *bookHit = NULL; // [HGM] book
3415     Boolean weird = FALSE, reqFlag = FALSE;
3416
3417     fromX = fromY = toX = toY = -1;
3418     
3419     newGame = FALSE;
3420
3421     if (appData.debugMode)
3422       fprintf(debugFP, _("Parsing board: %s\n"), string);
3423
3424     move_str[0] = NULLCHAR;
3425     elapsed_time[0] = NULLCHAR;
3426     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3427         int  i = 0, j;
3428         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3429             if(string[i] == ' ') { ranks++; files = 0; }
3430             else files++;
3431             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3432             i++;
3433         }
3434         for(j = 0; j <i; j++) board_chars[j] = string[j];
3435         board_chars[i] = '\0';
3436         string += i + 1;
3437     }
3438     n = sscanf(string, PATTERN, &to_play, &double_push,
3439                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3440                &gamenum, white, black, &relation, &basetime, &increment,
3441                &white_stren, &black_stren, &white_time, &black_time,
3442                &moveNum, str, elapsed_time, move_str, &ics_flip,
3443                &ticking);
3444
3445     if (n < 21) {
3446         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3447         DisplayError(str, 0);
3448         return;
3449     }
3450
3451     /* Convert the move number to internal form */
3452     moveNum = (moveNum - 1) * 2;
3453     if (to_play == 'B') moveNum++;
3454     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3455       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3456                         0, 1);
3457       return;
3458     }
3459     
3460     switch (relation) {
3461       case RELATION_OBSERVING_PLAYED:
3462       case RELATION_OBSERVING_STATIC:
3463         if (gamenum == -1) {
3464             /* Old ICC buglet */
3465             relation = RELATION_OBSERVING_STATIC;
3466         }
3467         newGameMode = IcsObserving;
3468         break;
3469       case RELATION_PLAYING_MYMOVE:
3470       case RELATION_PLAYING_NOTMYMOVE:
3471         newGameMode =
3472           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3473             IcsPlayingWhite : IcsPlayingBlack;
3474         break;
3475       case RELATION_EXAMINING:
3476         newGameMode = IcsExamining;
3477         break;
3478       case RELATION_ISOLATED_BOARD:
3479       default:
3480         /* Just display this board.  If user was doing something else,
3481            we will forget about it until the next board comes. */ 
3482         newGameMode = IcsIdle;
3483         break;
3484       case RELATION_STARTING_POSITION:
3485         newGameMode = gameMode;
3486         break;
3487     }
3488     
3489     /* Modify behavior for initial board display on move listing
3490        of wild games.
3491        */
3492     switch (ics_getting_history) {
3493       case H_FALSE:
3494       case H_REQUESTED:
3495         break;
3496       case H_GOT_REQ_HEADER:
3497       case H_GOT_UNREQ_HEADER:
3498         /* This is the initial position of the current game */
3499         gamenum = ics_gamenum;
3500         moveNum = 0;            /* old ICS bug workaround */
3501         if (to_play == 'B') {
3502           startedFromSetupPosition = TRUE;
3503           blackPlaysFirst = TRUE;
3504           moveNum = 1;
3505           if (forwardMostMove == 0) forwardMostMove = 1;
3506           if (backwardMostMove == 0) backwardMostMove = 1;
3507           if (currentMove == 0) currentMove = 1;
3508         }
3509         newGameMode = gameMode;
3510         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3511         break;
3512       case H_GOT_UNWANTED_HEADER:
3513         /* This is an initial board that we don't want */
3514         return;
3515       case H_GETTING_MOVES:
3516         /* Should not happen */
3517         DisplayError(_("Error gathering move list: extra board"), 0);
3518         ics_getting_history = H_FALSE;
3519         return;
3520     }
3521
3522    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3523                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3524      /* [HGM] We seem to have switched variant unexpectedly
3525       * Try to guess new variant from board size
3526       */
3527           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3528           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3529           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3530           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3531           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3532           if(!weird) newVariant = VariantNormal;
3533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3534           /* Get a move list just to see the header, which
3535              will tell us whether this is really bug or zh */
3536           if (ics_getting_history == H_FALSE) {
3537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3538             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3539             SendToICS(str);
3540           }
3541     }
3542     
3543     /* Take action if this is the first board of a new game, or of a
3544        different game than is currently being displayed.  */
3545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3546         relation == RELATION_ISOLATED_BOARD) {
3547         
3548         /* Forget the old game and get the history (if any) of the new one */
3549         if (gameMode != BeginningOfGame) {
3550           Reset(TRUE, TRUE);
3551         }
3552         newGame = TRUE;
3553         if (appData.autoRaiseBoard) BoardToTop();
3554         prevMove = -3;
3555         if (gamenum == -1) {
3556             newGameMode = IcsIdle;
3557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3558                    appData.getMoveList && !reqFlag) {
3559             /* Need to get game history */
3560             ics_getting_history = H_REQUESTED;
3561             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3562             SendToICS(str);
3563         }
3564         
3565         /* Initially flip the board to have black on the bottom if playing
3566            black or if the ICS flip flag is set, but let the user change
3567            it with the Flip View button. */
3568         flipView = appData.autoFlipView ? 
3569           (newGameMode == IcsPlayingBlack) || ics_flip :
3570           appData.flipView;
3571         
3572         /* Done with values from previous mode; copy in new ones */
3573         gameMode = newGameMode;
3574         ModeHighlight();
3575         ics_gamenum = gamenum;
3576         if (gamenum == gs_gamenum) {
3577             int klen = strlen(gs_kind);
3578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3579             sprintf(str, "ICS %s", gs_kind);
3580             gameInfo.event = StrSave(str);
3581         } else {
3582             gameInfo.event = StrSave("ICS game");
3583         }
3584         gameInfo.site = StrSave(appData.icsHost);
3585         gameInfo.date = PGNDate();
3586         gameInfo.round = StrSave("-");
3587         gameInfo.white = StrSave(white);
3588         gameInfo.black = StrSave(black);
3589         timeControl = basetime * 60 * 1000;
3590         timeControl_2 = 0;
3591         timeIncrement = increment * 1000;
3592         movesPerSession = 0;
3593         gameInfo.timeControl = TimeControlTagValue();
3594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3595   if (appData.debugMode) {
3596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3598     setbuf(debugFP, NULL);
3599   }
3600
3601         gameInfo.outOfBook = NULL;
3602         
3603         /* Do we have the ratings? */
3604         if (strcmp(player1Name, white) == 0 &&
3605             strcmp(player2Name, black) == 0) {
3606             if (appData.debugMode)
3607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3608                       player1Rating, player2Rating);
3609             gameInfo.whiteRating = player1Rating;
3610             gameInfo.blackRating = player2Rating;
3611         } else if (strcmp(player2Name, white) == 0 &&
3612                    strcmp(player1Name, black) == 0) {
3613             if (appData.debugMode)
3614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3615                       player2Rating, player1Rating);
3616             gameInfo.whiteRating = player2Rating;
3617             gameInfo.blackRating = player1Rating;
3618         }
3619         player1Name[0] = player2Name[0] = NULLCHAR;
3620
3621         /* Silence shouts if requested */
3622         if (appData.quietPlay &&
3623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3624             SendToICS(ics_prefix);
3625             SendToICS("set shout 0\n");
3626         }
3627     }
3628     
3629     /* Deal with midgame name changes */
3630     if (!newGame) {
3631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3632             if (gameInfo.white) free(gameInfo.white);
3633             gameInfo.white = StrSave(white);
3634         }
3635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3636             if (gameInfo.black) free(gameInfo.black);
3637             gameInfo.black = StrSave(black);
3638         }
3639     }
3640     
3641     /* Throw away game result if anything actually changes in examine mode */
3642     if (gameMode == IcsExamining && !newGame) {
3643         gameInfo.result = GameUnfinished;
3644         if (gameInfo.resultDetails != NULL) {
3645             free(gameInfo.resultDetails);
3646             gameInfo.resultDetails = NULL;
3647         }
3648     }
3649     
3650     /* In pausing && IcsExamining mode, we ignore boards coming
3651        in if they are in a different variation than we are. */
3652     if (pauseExamInvalid) return;
3653     if (pausing && gameMode == IcsExamining) {
3654         if (moveNum <= pauseExamForwardMostMove) {
3655             pauseExamInvalid = TRUE;
3656             forwardMostMove = pauseExamForwardMostMove;
3657             return;
3658         }
3659     }
3660     
3661   if (appData.debugMode) {
3662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3663   }
3664     /* Parse the board */
3665     for (k = 0; k < ranks; k++) {
3666       for (j = 0; j < files; j++)
3667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3668       if(gameInfo.holdingsWidth > 1) {
3669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3671       }
3672     }
3673     CopyBoard(boards[moveNum], board);
3674     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3675     if (moveNum == 0) {
3676         startedFromSetupPosition =
3677           !CompareBoards(board, initialPosition);
3678         if(startedFromSetupPosition)
3679             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3680     }
3681
3682     /* [HGM] Set castling rights. Take the outermost Rooks,
3683        to make it also work for FRC opening positions. Note that board12
3684        is really defective for later FRC positions, as it has no way to
3685        indicate which Rook can castle if they are on the same side of King.
3686        For the initial position we grant rights to the outermost Rooks,
3687        and remember thos rights, and we then copy them on positions
3688        later in an FRC game. This means WB might not recognize castlings with
3689        Rooks that have moved back to their original position as illegal,
3690        but in ICS mode that is not its job anyway.
3691     */
3692     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3693     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3694
3695         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3696             if(board[0][i] == WhiteRook) j = i;
3697         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3698         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3699             if(board[0][i] == WhiteRook) j = i;
3700         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3702             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707
3708         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3709         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3710             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712             if(board[BOARD_HEIGHT-1][k] == bKing)
3713                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3714     } else { int r;
3715         r = boards[moveNum][CASTLING][0] = initialRights[0];
3716         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3717         r = boards[moveNum][CASTLING][1] = initialRights[1];
3718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3719         r = boards[moveNum][CASTLING][3] = initialRights[3];
3720         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3721         r = boards[moveNum][CASTLING][4] = initialRights[4];
3722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3723         /* wildcastle kludge: always assume King has rights */
3724         r = boards[moveNum][CASTLING][2] = initialRights[2];
3725         r = boards[moveNum][CASTLING][5] = initialRights[5];
3726     }
3727     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3728     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3729
3730     
3731     if (ics_getting_history == H_GOT_REQ_HEADER ||
3732         ics_getting_history == H_GOT_UNREQ_HEADER) {
3733         /* This was an initial position from a move list, not
3734            the current position */
3735         return;
3736     }
3737     
3738     /* Update currentMove and known move number limits */
3739     newMove = newGame || moveNum > forwardMostMove;
3740
3741     if (newGame) {
3742         forwardMostMove = backwardMostMove = currentMove = moveNum;
3743         if (gameMode == IcsExamining && moveNum == 0) {
3744           /* Workaround for ICS limitation: we are not told the wild
3745              type when starting to examine a game.  But if we ask for
3746              the move list, the move list header will tell us */
3747             ics_getting_history = H_REQUESTED;
3748             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3749             SendToICS(str);
3750         }
3751     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3752                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3753 #if ZIPPY
3754         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3755         /* [HGM] applied this also to an engine that is silently watching        */
3756         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3757             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3758             gameInfo.variant == currentlyInitializedVariant) {
3759           takeback = forwardMostMove - moveNum;
3760           for (i = 0; i < takeback; i++) {
3761             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3762             SendToProgram("undo\n", &first);
3763           }
3764         }
3765 #endif
3766
3767         forwardMostMove = moveNum;
3768         if (!pausing || currentMove > forwardMostMove)
3769           currentMove = forwardMostMove;
3770     } else {
3771         /* New part of history that is not contiguous with old part */ 
3772         if (pausing && gameMode == IcsExamining) {
3773             pauseExamInvalid = TRUE;
3774             forwardMostMove = pauseExamForwardMostMove;
3775             return;
3776         }
3777         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3778 #if ZIPPY
3779             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3780                 // [HGM] when we will receive the move list we now request, it will be
3781                 // fed to the engine from the first move on. So if the engine is not
3782                 // in the initial position now, bring it there.
3783                 InitChessProgram(&first, 0);
3784             }
3785 #endif
3786             ics_getting_history = H_REQUESTED;
3787             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3788             SendToICS(str);
3789         }
3790         forwardMostMove = backwardMostMove = currentMove = moveNum;
3791     }
3792     
3793     /* Update the clocks */
3794     if (strchr(elapsed_time, '.')) {
3795       /* Time is in ms */
3796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3798     } else {
3799       /* Time is in seconds */
3800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3802     }
3803       
3804
3805 #if ZIPPY
3806     if (appData.zippyPlay && newGame &&
3807         gameMode != IcsObserving && gameMode != IcsIdle &&
3808         gameMode != IcsExamining)
3809       ZippyFirstBoard(moveNum, basetime, increment);
3810 #endif
3811     
3812     /* Put the move on the move list, first converting
3813        to canonical algebraic form. */
3814     if (moveNum > 0) {
3815   if (appData.debugMode) {
3816     if (appData.debugMode) { int f = forwardMostMove;
3817         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3818                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3819                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3820     }
3821     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3822     fprintf(debugFP, "moveNum = %d\n", moveNum);
3823     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3824     setbuf(debugFP, NULL);
3825   }
3826         if (moveNum <= backwardMostMove) {
3827             /* We don't know what the board looked like before
3828                this move.  Punt. */
3829             strcpy(parseList[moveNum - 1], move_str);
3830             strcat(parseList[moveNum - 1], " ");
3831             strcat(parseList[moveNum - 1], elapsed_time);
3832             moveList[moveNum - 1][0] = NULLCHAR;
3833         } else if (strcmp(move_str, "none") == 0) {
3834             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3835             /* Again, we don't know what the board looked like;
3836                this is really the start of the game. */
3837             parseList[moveNum - 1][0] = NULLCHAR;
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             backwardMostMove = moveNum;
3840             startedFromSetupPosition = TRUE;
3841             fromX = fromY = toX = toY = -1;
3842         } else {
3843           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3844           //                 So we parse the long-algebraic move string in stead of the SAN move
3845           int valid; char buf[MSG_SIZ], *prom;
3846
3847           // str looks something like "Q/a1-a2"; kill the slash
3848           if(str[1] == '/') 
3849                 sprintf(buf, "%c%s", str[0], str+2);
3850           else  strcpy(buf, str); // might be castling
3851           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3852                 strcat(buf, prom); // long move lacks promo specification!
3853           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3854                 if(appData.debugMode) 
3855                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3856                 strcpy(move_str, buf);
3857           }
3858           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3859                                 &fromX, &fromY, &toX, &toY, &promoChar)
3860                || ParseOneMove(buf, moveNum - 1, &moveType,
3861                                 &fromX, &fromY, &toX, &toY, &promoChar);
3862           // end of long SAN patch
3863           if (valid) {
3864             (void) CoordsToAlgebraic(boards[moveNum - 1],
3865                                      PosFlags(moveNum - 1),
3866                                      fromY, fromX, toY, toX, promoChar,
3867                                      parseList[moveNum-1]);
3868             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3869               case MT_NONE:
3870               case MT_STALEMATE:
3871               default:
3872                 break;
3873               case MT_CHECK:
3874                 if(gameInfo.variant != VariantShogi)
3875                     strcat(parseList[moveNum - 1], "+");
3876                 break;
3877               case MT_CHECKMATE:
3878               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3879                 strcat(parseList[moveNum - 1], "#");
3880                 break;
3881             }
3882             strcat(parseList[moveNum - 1], " ");
3883             strcat(parseList[moveNum - 1], elapsed_time);
3884             /* currentMoveString is set as a side-effect of ParseOneMove */
3885             strcpy(moveList[moveNum - 1], currentMoveString);
3886             strcat(moveList[moveNum - 1], "\n");
3887           } else {
3888             /* Move from ICS was illegal!?  Punt. */
3889   if (appData.debugMode) {
3890     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3891     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3892   }
3893             strcpy(parseList[moveNum - 1], move_str);
3894             strcat(parseList[moveNum - 1], " ");
3895             strcat(parseList[moveNum - 1], elapsed_time);
3896             moveList[moveNum - 1][0] = NULLCHAR;
3897             fromX = fromY = toX = toY = -1;
3898           }
3899         }
3900   if (appData.debugMode) {
3901     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3902     setbuf(debugFP, NULL);
3903   }
3904
3905 #if ZIPPY
3906         /* Send move to chess program (BEFORE animating it). */
3907         if (appData.zippyPlay && !newGame && newMove && 
3908            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3909
3910             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3911                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3912                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3913                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3914                             move_str);
3915                     DisplayError(str, 0);
3916                 } else {
3917                     if (first.sendTime) {
3918                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3919                     }
3920                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3921                     if (firstMove && !bookHit) {
3922                         firstMove = FALSE;
3923                         if (first.useColors) {
3924                           SendToProgram(gameMode == IcsPlayingWhite ?
3925                                         "white\ngo\n" :
3926                                         "black\ngo\n", &first);
3927                         } else {
3928                           SendToProgram("go\n", &first);
3929                         }
3930                         first.maybeThinking = TRUE;
3931                     }
3932                 }
3933             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3934               if (moveList[moveNum - 1][0] == NULLCHAR) {
3935                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3936                 DisplayError(str, 0);
3937               } else {
3938                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3939                 SendMoveToProgram(moveNum - 1, &first);
3940               }
3941             }
3942         }
3943 #endif
3944     }
3945
3946     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3947         /* If move comes from a remote source, animate it.  If it
3948            isn't remote, it will have already been animated. */
3949         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3950             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3951         }
3952         if (!pausing && appData.highlightLastMove) {
3953             SetHighlights(fromX, fromY, toX, toY);
3954         }
3955     }
3956     
3957     /* Start the clocks */
3958     whiteFlag = blackFlag = FALSE;
3959     appData.clockMode = !(basetime == 0 && increment == 0);
3960     if (ticking == 0) {
3961       ics_clock_paused = TRUE;
3962       StopClocks();
3963     } else if (ticking == 1) {
3964       ics_clock_paused = FALSE;
3965     }
3966     if (gameMode == IcsIdle ||
3967         relation == RELATION_OBSERVING_STATIC ||
3968         relation == RELATION_EXAMINING ||
3969         ics_clock_paused)
3970       DisplayBothClocks();
3971     else
3972       StartClocks();
3973     
3974     /* Display opponents and material strengths */
3975     if (gameInfo.variant != VariantBughouse &&
3976         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3977         if (tinyLayout || smallLayout) {
3978             if(gameInfo.variant == VariantNormal)
3979                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3981                     basetime, increment);
3982             else
3983                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3984                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3985                     basetime, increment, (int) gameInfo.variant);
3986         } else {
3987             if(gameInfo.variant == VariantNormal)
3988                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3989                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3990                     basetime, increment);
3991             else
3992                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3994                     basetime, increment, VariantName(gameInfo.variant));
3995         }
3996         DisplayTitle(str);
3997   if (appData.debugMode) {
3998     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3999   }
4000     }
4001
4002    
4003     /* Display the board */
4004     if (!pausing && !appData.noGUI) {
4005       
4006       if (appData.premove)
4007           if (!gotPremove || 
4008              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4009              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4010               ClearPremoveHighlights();
4011
4012       DrawPosition(FALSE, boards[currentMove]);
4013       DisplayMove(moveNum - 1);
4014       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4015             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4016               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4017         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4018       }
4019     }
4020
4021     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4022 #if ZIPPY
4023     if(bookHit) { // [HGM] book: simulate book reply
4024         static char bookMove[MSG_SIZ]; // a bit generous?
4025
4026         programStats.nodes = programStats.depth = programStats.time = 
4027         programStats.score = programStats.got_only_move = 0;
4028         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4029
4030         strcpy(bookMove, "move ");
4031         strcat(bookMove, bookHit);
4032         HandleMachineMove(bookMove, &first);
4033     }
4034 #endif
4035 }
4036
4037 void
4038 GetMoveListEvent()
4039 {
4040     char buf[MSG_SIZ];
4041     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4042         ics_getting_history = H_REQUESTED;
4043         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4044         SendToICS(buf);
4045     }
4046 }
4047
4048 void
4049 AnalysisPeriodicEvent(force)
4050      int force;
4051 {
4052     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4053          && !force) || !appData.periodicUpdates)
4054       return;
4055
4056     /* Send . command to Crafty to collect stats */
4057     SendToProgram(".\n", &first);
4058
4059     /* Don't send another until we get a response (this makes
4060        us stop sending to old Crafty's which don't understand
4061        the "." command (sending illegal cmds resets node count & time,
4062        which looks bad)) */
4063     programStats.ok_to_send = 0;
4064 }
4065
4066 void ics_update_width(new_width)
4067         int new_width;
4068 {
4069         ics_printf("set width %d\n", new_width);
4070 }
4071
4072 void
4073 SendMoveToProgram(moveNum, cps)
4074      int moveNum;
4075      ChessProgramState *cps;
4076 {
4077     char buf[MSG_SIZ];
4078
4079     if (cps->useUsermove) {
4080       SendToProgram("usermove ", cps);
4081     }
4082     if (cps->useSAN) {
4083       char *space;
4084       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4085         int len = space - parseList[moveNum];
4086         memcpy(buf, parseList[moveNum], len);
4087         buf[len++] = '\n';
4088         buf[len] = NULLCHAR;
4089       } else {
4090         sprintf(buf, "%s\n", parseList[moveNum]);
4091       }
4092       SendToProgram(buf, cps);
4093     } else {
4094       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4095         AlphaRank(moveList[moveNum], 4);
4096         SendToProgram(moveList[moveNum], cps);
4097         AlphaRank(moveList[moveNum], 4); // and back
4098       } else
4099       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4100        * the engine. It would be nice to have a better way to identify castle 
4101        * moves here. */
4102       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4103                                                                          && cps->useOOCastle) {
4104         int fromX = moveList[moveNum][0] - AAA; 
4105         int fromY = moveList[moveNum][1] - ONE;
4106         int toX = moveList[moveNum][2] - AAA; 
4107         int toY = moveList[moveNum][3] - ONE;
4108         if((boards[moveNum][fromY][fromX] == WhiteKing 
4109             && boards[moveNum][toY][toX] == WhiteRook)
4110            || (boards[moveNum][fromY][fromX] == BlackKing 
4111                && boards[moveNum][toY][toX] == BlackRook)) {
4112           if(toX > fromX) SendToProgram("O-O\n", cps);
4113           else SendToProgram("O-O-O\n", cps);
4114         }
4115         else SendToProgram(moveList[moveNum], cps);
4116       }
4117       else SendToProgram(moveList[moveNum], cps);
4118       /* End of additions by Tord */
4119     }
4120
4121     /* [HGM] setting up the opening has brought engine in force mode! */
4122     /*       Send 'go' if we are in a mode where machine should play. */
4123     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4124         (gameMode == TwoMachinesPlay   ||
4125 #ifdef ZIPPY
4126          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4127 #endif
4128          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4129         SendToProgram("go\n", cps);
4130   if (appData.debugMode) {
4131     fprintf(debugFP, "(extra)\n");
4132   }
4133     }
4134     setboardSpoiledMachineBlack = 0;
4135 }
4136
4137 void
4138 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4139      ChessMove moveType;
4140      int fromX, fromY, toX, toY;
4141 {
4142     char user_move[MSG_SIZ];
4143
4144     switch (moveType) {
4145       default:
4146         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4147                 (int)moveType, fromX, fromY, toX, toY);
4148         DisplayError(user_move + strlen("say "), 0);
4149         break;
4150       case WhiteKingSideCastle:
4151       case BlackKingSideCastle:
4152       case WhiteQueenSideCastleWild:
4153       case BlackQueenSideCastleWild:
4154       /* PUSH Fabien */
4155       case WhiteHSideCastleFR:
4156       case BlackHSideCastleFR:
4157       /* POP Fabien */
4158         sprintf(user_move, "o-o\n");
4159         break;
4160       case WhiteQueenSideCastle:
4161       case BlackQueenSideCastle:
4162       case WhiteKingSideCastleWild:
4163       case BlackKingSideCastleWild:
4164       /* PUSH Fabien */
4165       case WhiteASideCastleFR:
4166       case BlackASideCastleFR:
4167       /* POP Fabien */
4168         sprintf(user_move, "o-o-o\n");
4169         break;
4170       case WhitePromotionQueen:
4171       case BlackPromotionQueen:
4172       case WhitePromotionRook:
4173       case BlackPromotionRook:
4174       case WhitePromotionBishop:
4175       case BlackPromotionBishop:
4176       case WhitePromotionKnight:
4177       case BlackPromotionKnight:
4178       case WhitePromotionKing:
4179       case BlackPromotionKing:
4180       case WhitePromotionChancellor:
4181       case BlackPromotionChancellor:
4182       case WhitePromotionArchbishop:
4183       case BlackPromotionArchbishop:
4184         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4185             sprintf(user_move, "%c%c%c%c=%c\n",
4186                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4187                 PieceToChar(WhiteFerz));
4188         else if(gameInfo.variant == VariantGreat)
4189             sprintf(user_move, "%c%c%c%c=%c\n",
4190                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191                 PieceToChar(WhiteMan));
4192         else
4193             sprintf(user_move, "%c%c%c%c=%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195                 PieceToChar(PromoPiece(moveType)));
4196         break;
4197       case WhiteDrop:
4198       case BlackDrop:
4199         sprintf(user_move, "%c@%c%c\n",
4200                 ToUpper(PieceToChar((ChessSquare) fromX)),
4201                 AAA + toX, ONE + toY);
4202         break;
4203       case NormalMove:
4204       case WhiteCapturesEnPassant:
4205       case BlackCapturesEnPassant:
4206       case IllegalMove:  /* could be a variant we don't quite understand */
4207         sprintf(user_move, "%c%c%c%c\n",
4208                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4209         break;
4210     }
4211     SendToICS(user_move);
4212     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4213         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4214 }
4215
4216 void
4217 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4218      int rf, ff, rt, ft;
4219      char promoChar;
4220      char move[7];
4221 {
4222     if (rf == DROP_RANK) {
4223         sprintf(move, "%c@%c%c\n",
4224                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4225     } else {
4226         if (promoChar == 'x' || promoChar == NULLCHAR) {
4227             sprintf(move, "%c%c%c%c\n",
4228                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4229         } else {
4230             sprintf(move, "%c%c%c%c%c\n",
4231                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4232         }
4233     }
4234 }
4235
4236 void
4237 ProcessICSInitScript(f)
4238      FILE *f;
4239 {
4240     char buf[MSG_SIZ];
4241
4242     while (fgets(buf, MSG_SIZ, f)) {
4243         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4244     }
4245
4246     fclose(f);
4247 }
4248
4249
4250 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4251 void
4252 AlphaRank(char *move, int n)
4253 {
4254 //    char *p = move, c; int x, y;
4255
4256     if (appData.debugMode) {
4257         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4258     }
4259
4260     if(move[1]=='*' && 
4261        move[2]>='0' && move[2]<='9' &&
4262        move[3]>='a' && move[3]<='x'    ) {
4263         move[1] = '@';
4264         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4265         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4266     } else
4267     if(move[0]>='0' && move[0]<='9' &&
4268        move[1]>='a' && move[1]<='x' &&
4269        move[2]>='0' && move[2]<='9' &&
4270        move[3]>='a' && move[3]<='x'    ) {
4271         /* input move, Shogi -> normal */
4272         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4273         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4274         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4275         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4276     } else
4277     if(move[1]=='@' &&
4278        move[3]>='0' && move[3]<='9' &&
4279        move[2]>='a' && move[2]<='x'    ) {
4280         move[1] = '*';
4281         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4282         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4283     } else
4284     if(
4285        move[0]>='a' && move[0]<='x' &&
4286        move[3]>='0' && move[3]<='9' &&
4287        move[2]>='a' && move[2]<='x'    ) {
4288          /* output move, normal -> Shogi */
4289         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4290         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4291         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4292         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4293         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4294     }
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "   out = '%s'\n", move);
4297     }
4298 }
4299
4300 /* Parser for moves from gnuchess, ICS, or user typein box */
4301 Boolean
4302 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4303      char *move;
4304      int moveNum;
4305      ChessMove *moveType;
4306      int *fromX, *fromY, *toX, *toY;
4307      char *promoChar;
4308 {       
4309     if (appData.debugMode) {
4310         fprintf(debugFP, "move to parse: %s\n", move);
4311     }
4312     *moveType = yylexstr(moveNum, move);
4313
4314     switch (*moveType) {
4315       case WhitePromotionChancellor:
4316       case BlackPromotionChancellor:
4317       case WhitePromotionArchbishop:
4318       case BlackPromotionArchbishop:
4319       case WhitePromotionQueen:
4320       case BlackPromotionQueen:
4321       case WhitePromotionRook:
4322       case BlackPromotionRook:
4323       case WhitePromotionBishop:
4324       case BlackPromotionBishop:
4325       case WhitePromotionKnight:
4326       case BlackPromotionKnight:
4327       case WhitePromotionKing:
4328       case BlackPromotionKing:
4329       case NormalMove:
4330       case WhiteCapturesEnPassant:
4331       case BlackCapturesEnPassant:
4332       case WhiteKingSideCastle:
4333       case WhiteQueenSideCastle:
4334       case BlackKingSideCastle:
4335       case BlackQueenSideCastle:
4336       case WhiteKingSideCastleWild:
4337       case WhiteQueenSideCastleWild:
4338       case BlackKingSideCastleWild:
4339       case BlackQueenSideCastleWild:
4340       /* Code added by Tord: */
4341       case WhiteHSideCastleFR:
4342       case WhiteASideCastleFR:
4343       case BlackHSideCastleFR:
4344       case BlackASideCastleFR:
4345       /* End of code added by Tord */
4346       case IllegalMove:         /* bug or odd chess variant */
4347         *fromX = currentMoveString[0] - AAA;
4348         *fromY = currentMoveString[1] - ONE;
4349         *toX = currentMoveString[2] - AAA;
4350         *toY = currentMoveString[3] - ONE;
4351         *promoChar = currentMoveString[4];
4352         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4353             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4354     if (appData.debugMode) {
4355         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4356     }
4357             *fromX = *fromY = *toX = *toY = 0;
4358             return FALSE;
4359         }
4360         if (appData.testLegality) {
4361           return (*moveType != IllegalMove);
4362         } else {
4363           return !(fromX == fromY && toX == toY);
4364         }
4365
4366       case WhiteDrop:
4367       case BlackDrop:
4368         *fromX = *moveType == WhiteDrop ?
4369           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4370           (int) CharToPiece(ToLower(currentMoveString[0]));
4371         *fromY = DROP_RANK;
4372         *toX = currentMoveString[2] - AAA;
4373         *toY = currentMoveString[3] - ONE;
4374         *promoChar = NULLCHAR;
4375         return TRUE;
4376
4377       case AmbiguousMove:
4378       case ImpossibleMove:
4379       case (ChessMove) 0:       /* end of file */
4380       case ElapsedTime:
4381       case Comment:
4382       case PGNTag:
4383       case NAG:
4384       case WhiteWins:
4385       case BlackWins:
4386       case GameIsDrawn:
4387       default:
4388     if (appData.debugMode) {
4389         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4390     }
4391         /* bug? */
4392         *fromX = *fromY = *toX = *toY = 0;
4393         *promoChar = NULLCHAR;
4394         return FALSE;
4395     }
4396 }
4397
4398 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4399 // All positions will have equal probability, but the current method will not provide a unique
4400 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4401 #define DARK 1
4402 #define LITE 2
4403 #define ANY 3
4404
4405 int squaresLeft[4];
4406 int piecesLeft[(int)BlackPawn];
4407 int seed, nrOfShuffles;
4408
4409 void GetPositionNumber()
4410 {       // sets global variable seed
4411         int i;
4412
4413         seed = appData.defaultFrcPosition;
4414         if(seed < 0) { // randomize based on time for negative FRC position numbers
4415                 for(i=0; i<50; i++) seed += random();
4416                 seed = random() ^ random() >> 8 ^ random() << 8;
4417                 if(seed<0) seed = -seed;
4418         }
4419 }
4420
4421 int put(Board board, int pieceType, int rank, int n, int shade)
4422 // put the piece on the (n-1)-th empty squares of the given shade
4423 {
4424         int i;
4425
4426         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4427                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4428                         board[rank][i] = (ChessSquare) pieceType;
4429                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4430                         squaresLeft[ANY]--;
4431                         piecesLeft[pieceType]--; 
4432                         return i;
4433                 }
4434         }
4435         return -1;
4436 }
4437
4438
4439 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4440 // calculate where the next piece goes, (any empty square), and put it there
4441 {
4442         int i;
4443
4444         i = seed % squaresLeft[shade];
4445         nrOfShuffles *= squaresLeft[shade];
4446         seed /= squaresLeft[shade];
4447         put(board, pieceType, rank, i, shade);
4448 }
4449
4450 void AddTwoPieces(Board board, int pieceType, int rank)
4451 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4452 {
4453         int i, n=squaresLeft[ANY], j=n-1, k;
4454
4455         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4456         i = seed % k;  // pick one
4457         nrOfShuffles *= k;
4458         seed /= k;
4459         while(i >= j) i -= j--;
4460         j = n - 1 - j; i += j;
4461         put(board, pieceType, rank, j, ANY);
4462         put(board, pieceType, rank, i, ANY);
4463 }
4464
4465 void SetUpShuffle(Board board, int number)
4466 {
4467         int i, p, first=1;
4468
4469         GetPositionNumber(); nrOfShuffles = 1;
4470
4471         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4472         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4473         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4474
4475         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4476
4477         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4478             p = (int) board[0][i];
4479             if(p < (int) BlackPawn) piecesLeft[p] ++;
4480             board[0][i] = EmptySquare;
4481         }
4482
4483         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4484             // shuffles restricted to allow normal castling put KRR first
4485             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4486                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4487             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4488                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4489             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4490                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4491             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4492                 put(board, WhiteRook, 0, 0, ANY);
4493             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4494         }
4495
4496         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4497             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4498             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4499                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4500                 while(piecesLeft[p] >= 2) {
4501                     AddOnePiece(board, p, 0, LITE);
4502                     AddOnePiece(board, p, 0, DARK);
4503                 }
4504                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4505             }
4506
4507         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4508             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4509             // but we leave King and Rooks for last, to possibly obey FRC restriction
4510             if(p == (int)WhiteRook) continue;
4511             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4512             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4513         }
4514
4515         // now everything is placed, except perhaps King (Unicorn) and Rooks
4516
4517         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4518             // Last King gets castling rights
4519             while(piecesLeft[(int)WhiteUnicorn]) {
4520                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4521                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4522             }
4523
4524             while(piecesLeft[(int)WhiteKing]) {
4525                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4526                 initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4527             }
4528
4529
4530         } else {
4531             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4532             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4533         }
4534
4535         // Only Rooks can be left; simply place them all
4536         while(piecesLeft[(int)WhiteRook]) {
4537                 i = put(board, WhiteRook, 0, 0, ANY);
4538                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4539                         if(first) {
4540                                 first=0;
4541                                 initialRights[1]  = initialRights[4]  = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4542                         }
4543                         initialRights[0]  = initialRights[3]  = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4544                 }
4545         }
4546         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4547             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4548         }
4549
4550         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4551 }
4552
4553 int SetCharTable( char *table, const char * map )
4554 /* [HGM] moved here from winboard.c because of its general usefulness */
4555 /*       Basically a safe strcpy that uses the last character as King */
4556 {
4557     int result = FALSE; int NrPieces;
4558
4559     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4560                     && NrPieces >= 12 && !(NrPieces&1)) {
4561         int i; /* [HGM] Accept even length from 12 to 34 */
4562
4563         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4564         for( i=0; i<NrPieces/2-1; i++ ) {
4565             table[i] = map[i];
4566             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4567         }
4568         table[(int) WhiteKing]  = map[NrPieces/2-1];
4569         table[(int) BlackKing]  = map[NrPieces-1];
4570
4571         result = TRUE;
4572     }
4573
4574     return result;
4575 }
4576
4577 void Prelude(Board board)
4578 {       // [HGM] superchess: random selection of exo-pieces
4579         int i, j, k; ChessSquare p; 
4580         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4581
4582         GetPositionNumber(); // use FRC position number
4583
4584         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4585             SetCharTable(pieceToChar, appData.pieceToCharTable);
4586             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4587                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4588         }
4589
4590         j = seed%4;                 seed /= 4; 
4591         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4592         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4593         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4594         j = seed%3 + (seed%3 >= j); seed /= 3; 
4595         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4596         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4597         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4598         j = seed%3;                 seed /= 3; 
4599         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4600         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4601         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4602         j = seed%2 + (seed%2 >= j); seed /= 2; 
4603         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4604         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4605         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4606         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4607         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4608         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4609         put(board, exoPieces[0],    0, 0, ANY);
4610         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4611 }
4612
4613 void
4614 InitPosition(redraw)
4615      int redraw;
4616 {
4617     ChessSquare (* pieces)[BOARD_FILES];
4618     int i, j, pawnRow, overrule,
4619     oldx = gameInfo.boardWidth,
4620     oldy = gameInfo.boardHeight,
4621     oldh = gameInfo.holdingsWidth,
4622     oldv = gameInfo.variant;
4623
4624     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4625
4626     /* [AS] Initialize pv info list [HGM] and game status */
4627     {
4628         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4629             pvInfoList[i].depth = 0;
4630             boards[i][EP_STATUS] = EP_NONE;
4631             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4632         }
4633
4634         initialRulePlies = 0; /* 50-move counter start */
4635
4636         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4637         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4638     }
4639
4640     
4641     /* [HGM] logic here is completely changed. In stead of full positions */
4642     /* the initialized data only consist of the two backranks. The switch */
4643     /* selects which one we will use, which is than copied to the Board   */
4644     /* initialPosition, which for the rest is initialized by Pawns and    */
4645     /* empty squares. This initial position is then copied to boards[0],  */
4646     /* possibly after shuffling, so that it remains available.            */
4647
4648     gameInfo.holdingsWidth = 0; /* default board sizes */
4649     gameInfo.boardWidth    = 8;
4650     gameInfo.boardHeight   = 8;
4651     gameInfo.holdingsSize  = 0;
4652     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4653     for(i=0; i<BOARD_FILES-2; i++)
4654       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4655     initialPosition[EP_STATUS] = EP_NONE;
4656     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4657
4658     switch (gameInfo.variant) {
4659     case VariantFischeRandom:
4660       shuffleOpenings = TRUE;
4661     default:
4662       pieces = FIDEArray;
4663       break;
4664     case VariantShatranj:
4665       pieces = ShatranjArray;
4666       nrCastlingRights = 0;
4667       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4668       break;
4669     case VariantTwoKings:
4670       pieces = twoKingsArray;
4671       break;
4672     case VariantCapaRandom:
4673       shuffleOpenings = TRUE;
4674     case VariantCapablanca:
4675       pieces = CapablancaArray;
4676       gameInfo.boardWidth = 10;
4677       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4678       break;
4679     case VariantGothic:
4680       pieces = GothicArray;
4681       gameInfo.boardWidth = 10;
4682       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4683       break;
4684     case VariantJanus:
4685       pieces = JanusArray;
4686       gameInfo.boardWidth = 10;
4687       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4688       nrCastlingRights = 6;
4689         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4690         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4691         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4692         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4693         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4694         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4695       break;
4696     case VariantFalcon:
4697       pieces = FalconArray;
4698       gameInfo.boardWidth = 10;
4699       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4700       break;
4701     case VariantXiangqi:
4702       pieces = XiangqiArray;
4703       gameInfo.boardWidth  = 9;
4704       gameInfo.boardHeight = 10;
4705       nrCastlingRights = 0;
4706       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4707       break;
4708     case VariantShogi:
4709       pieces = ShogiArray;
4710       gameInfo.boardWidth  = 9;
4711       gameInfo.boardHeight = 9;
4712       gameInfo.holdingsSize = 7;
4713       nrCastlingRights = 0;
4714       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4715       break;
4716     case VariantCourier:
4717       pieces = CourierArray;
4718       gameInfo.boardWidth  = 12;
4719       nrCastlingRights = 0;
4720       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4721       break;
4722     case VariantKnightmate:
4723       pieces = KnightmateArray;
4724       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4725       break;
4726     case VariantFairy:
4727       pieces = fairyArray;
4728       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4729       break;
4730     case VariantGreat:
4731       pieces = GreatArray;
4732       gameInfo.boardWidth = 10;
4733       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4734       gameInfo.holdingsSize = 8;
4735       break;
4736     case VariantSuper:
4737       pieces = FIDEArray;
4738       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4739       gameInfo.holdingsSize = 8;
4740       startedFromSetupPosition = TRUE;
4741       break;
4742     case VariantCrazyhouse:
4743     case VariantBughouse:
4744       pieces = FIDEArray;
4745       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4746       gameInfo.holdingsSize = 5;
4747       break;
4748     case VariantWildCastle:
4749       pieces = FIDEArray;
4750       /* !!?shuffle with kings guaranteed to be on d or e file */
4751       shuffleOpenings = 1;
4752       break;
4753     case VariantNoCastle:
4754       pieces = FIDEArray;
4755       nrCastlingRights = 0;
4756       /* !!?unconstrained back-rank shuffle */
4757       shuffleOpenings = 1;
4758       break;
4759     }
4760
4761     overrule = 0;
4762     if(appData.NrFiles >= 0) {
4763         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4764         gameInfo.boardWidth = appData.NrFiles;
4765     }
4766     if(appData.NrRanks >= 0) {
4767         gameInfo.boardHeight = appData.NrRanks;
4768     }
4769     if(appData.holdingsSize >= 0) {
4770         i = appData.holdingsSize;
4771         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4772         gameInfo.holdingsSize = i;
4773     }
4774     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4775     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4776         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4777
4778     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4779     if(pawnRow < 1) pawnRow = 1;
4780
4781     /* User pieceToChar list overrules defaults */
4782     if(appData.pieceToCharTable != NULL)
4783         SetCharTable(pieceToChar, appData.pieceToCharTable);
4784
4785     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4786
4787         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4788             s = (ChessSquare) 0; /* account holding counts in guard band */
4789         for( i=0; i<BOARD_HEIGHT; i++ )
4790             initialPosition[i][j] = s;
4791
4792         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4793         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4794         initialPosition[pawnRow][j] = WhitePawn;
4795         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4796         if(gameInfo.variant == VariantXiangqi) {
4797             if(j&1) {
4798                 initialPosition[pawnRow][j] = 
4799                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4800                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4801                    initialPosition[2][j] = WhiteCannon;
4802                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4803                 }
4804             }
4805         }
4806         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4807     }
4808     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4809
4810             j=BOARD_LEFT+1;
4811             initialPosition[1][j] = WhiteBishop;
4812             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4813             j=BOARD_RGHT-2;
4814             initialPosition[1][j] = WhiteRook;
4815             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4816     }
4817
4818     if( nrCastlingRights == -1) {
4819         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4820         /*       This sets default castling rights from none to normal corners   */
4821         /* Variants with other castling rights must set them themselves above    */
4822         nrCastlingRights = 6;
4823        
4824         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4825         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4826         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4827         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4828         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4829         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4830      }
4831
4832      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4833      if(gameInfo.variant == VariantGreat) { // promotion commoners
4834         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4835         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4836         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4837         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4838      }
4839   if (appData.debugMode) {
4840     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4841   }
4842     if(shuffleOpenings) {
4843         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4844         startedFromSetupPosition = TRUE;
4845     }
4846     if(startedFromPositionFile) {
4847       /* [HGM] loadPos: use PositionFile for every new game */
4848       CopyBoard(initialPosition, filePosition);
4849       for(i=0; i<nrCastlingRights; i++)
4850           initialRights[i] = filePosition[CASTLING][i];
4851       startedFromSetupPosition = TRUE;
4852     }
4853
4854     CopyBoard(boards[0], initialPosition);
4855
4856     if(oldx != gameInfo.boardWidth ||
4857        oldy != gameInfo.boardHeight ||
4858        oldh != gameInfo.holdingsWidth
4859 #ifdef GOTHIC
4860        || oldv == VariantGothic ||        // For licensing popups
4861        gameInfo.variant == VariantGothic
4862 #endif
4863 #ifdef FALCON
4864        || oldv == VariantFalcon ||
4865        gameInfo.variant == VariantFalcon
4866 #endif
4867                                          )
4868             InitDrawingSizes(-2 ,0);
4869
4870     if (redraw)
4871       DrawPosition(TRUE, boards[currentMove]);
4872 }
4873
4874 void
4875 SendBoard(cps, moveNum)
4876      ChessProgramState *cps;
4877      int moveNum;
4878 {
4879     char message[MSG_SIZ];
4880     
4881     if (cps->useSetboard) {
4882       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4883       sprintf(message, "setboard %s\n", fen);
4884       SendToProgram(message, cps);
4885       free(fen);
4886
4887     } else {
4888       ChessSquare *bp;
4889       int i, j;
4890       /* Kludge to set black to move, avoiding the troublesome and now
4891        * deprecated "black" command.
4892        */
4893       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4894
4895       SendToProgram("edit\n", cps);
4896       SendToProgram("#\n", cps);
4897       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4898         bp = &boards[moveNum][i][BOARD_LEFT];
4899         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4900           if ((int) *bp < (int) BlackPawn) {
4901             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4902                     AAA + j, ONE + i);
4903             if(message[0] == '+' || message[0] == '~') {
4904                 sprintf(message, "%c%c%c+\n",
4905                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4906                         AAA + j, ONE + i);
4907             }
4908             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4909                 message[1] = BOARD_RGHT   - 1 - j + '1';
4910                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4911             }
4912             SendToProgram(message, cps);
4913           }
4914         }
4915       }
4916     
4917       SendToProgram("c\n", cps);
4918       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4919         bp = &boards[moveNum][i][BOARD_LEFT];
4920         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4921           if (((int) *bp != (int) EmptySquare)
4922               && ((int) *bp >= (int) BlackPawn)) {
4923             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4924                     AAA + j, ONE + i);
4925             if(message[0] == '+' || message[0] == '~') {
4926                 sprintf(message, "%c%c%c+\n",
4927                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4928                         AAA + j, ONE + i);
4929             }
4930             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4931                 message[1] = BOARD_RGHT   - 1 - j + '1';
4932                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4933             }
4934             SendToProgram(message, cps);
4935           }
4936         }
4937       }
4938     
4939       SendToProgram(".\n", cps);
4940     }
4941     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4942 }
4943
4944 int
4945 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4946 {
4947     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4948     /* [HGM] add Shogi promotions */
4949     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4950     ChessSquare piece;
4951     ChessMove moveType;
4952     Boolean premove;
4953
4954     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4955     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4956
4957     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4958       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4959         return FALSE;
4960
4961     piece = boards[currentMove][fromY][fromX];
4962     if(gameInfo.variant == VariantShogi) {
4963         promotionZoneSize = 3;
4964         highestPromotingPiece = (int)WhiteFerz;
4965     }
4966
4967     // next weed out all moves that do not touch the promotion zone at all
4968     if((int)piece >= BlackPawn) {
4969         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4970              return FALSE;
4971         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4972     } else {
4973         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4974            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4975     }
4976
4977     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4978
4979     // weed out mandatory Shogi promotions
4980     if(gameInfo.variant == VariantShogi) {
4981         if(piece >= BlackPawn) {
4982             if(toY == 0 && piece == BlackPawn ||
4983                toY == 0 && piece == BlackQueen ||
4984                toY <= 1 && piece == BlackKnight) {
4985                 *promoChoice = '+';
4986                 return FALSE;
4987             }
4988         } else {
4989             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4990                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4991                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4992                 *promoChoice = '+';
4993                 return FALSE;
4994             }
4995         }
4996     }
4997
4998     // weed out obviously illegal Pawn moves
4999     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5000         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5001         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5002         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5003         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5004         // note we are not allowed to test for valid (non-)capture, due to premove
5005     }
5006
5007     // we either have a choice what to promote to, or (in Shogi) whether to promote
5008     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5009         *promoChoice = PieceToChar(BlackFerz);  // no choice
5010         return FALSE;
5011     }
5012     if(appData.alwaysPromoteToQueen) { // predetermined
5013         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5014              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5015         else *promoChoice = PieceToChar(BlackQueen);
5016         return FALSE;
5017     }
5018
5019     // suppress promotion popup on illegal moves that are not premoves
5020     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5021               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5022     if(appData.testLegality && !premove) {
5023         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5024                         fromY, fromX, toY, toX, NULLCHAR);
5025         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5026            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5027             return FALSE;
5028     }
5029
5030     return TRUE;
5031 }
5032
5033 int
5034 InPalace(row, column)
5035      int row, column;
5036 {   /* [HGM] for Xiangqi */
5037     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5038          column < (BOARD_WIDTH + 4)/2 &&
5039          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5040     return FALSE;
5041 }
5042
5043 int
5044 PieceForSquare (x, y)
5045      int x;
5046      int y;
5047 {
5048   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5049      return -1;
5050   else
5051      return boards[currentMove][y][x];
5052 }
5053
5054 int
5055 OKToStartUserMove(x, y)
5056      int x, y;
5057 {
5058     ChessSquare from_piece;
5059     int white_piece;
5060
5061     if (matchMode) return FALSE;
5062     if (gameMode == EditPosition) return TRUE;
5063
5064     if (x >= 0 && y >= 0)
5065       from_piece = boards[currentMove][y][x];
5066     else
5067       from_piece = EmptySquare;
5068
5069     if (from_piece == EmptySquare) return FALSE;
5070
5071     white_piece = (int)from_piece >= (int)WhitePawn &&
5072       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5073
5074     switch (gameMode) {
5075       case PlayFromGameFile:
5076       case AnalyzeFile:
5077       case TwoMachinesPlay:
5078       case EndOfGame:
5079         return FALSE;
5080
5081       case IcsObserving:
5082       case IcsIdle:
5083         return FALSE;
5084
5085       case MachinePlaysWhite:
5086       case IcsPlayingBlack:
5087         if (appData.zippyPlay) return FALSE;
5088         if (white_piece) {
5089             DisplayMoveError(_("You are playing Black"));
5090             return FALSE;
5091         }
5092         break;
5093
5094       case MachinePlaysBlack:
5095       case IcsPlayingWhite:
5096         if (appData.zippyPlay) return FALSE;
5097         if (!white_piece) {
5098             DisplayMoveError(_("You are playing White"));
5099             return FALSE;
5100         }
5101         break;
5102
5103       case EditGame:
5104         if (!white_piece && WhiteOnMove(currentMove)) {
5105             DisplayMoveError(_("It is White's turn"));
5106             return FALSE;
5107         }           
5108         if (white_piece && !WhiteOnMove(currentMove)) {
5109             DisplayMoveError(_("It is Black's turn"));
5110             return FALSE;
5111         }           
5112         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5113             /* Editing correspondence game history */
5114             /* Could disallow this or prompt for confirmation */
5115             cmailOldMove = -1;
5116         }
5117         break;
5118
5119       case BeginningOfGame:
5120         if (appData.icsActive) return FALSE;
5121         if (!appData.noChessProgram) {
5122             if (!white_piece) {
5123                 DisplayMoveError(_("You are playing White"));
5124                 return FALSE;
5125             }
5126         }
5127         break;
5128         
5129       case Training:
5130         if (!white_piece && WhiteOnMove(currentMove)) {
5131             DisplayMoveError(_("It is White's turn"));
5132             return FALSE;
5133         }           
5134         if (white_piece && !WhiteOnMove(currentMove)) {
5135             DisplayMoveError(_("It is Black's turn"));
5136             return FALSE;
5137         }           
5138         break;
5139
5140       default:
5141       case IcsExamining:
5142         break;
5143     }
5144     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5145         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5146         && gameMode != AnalyzeFile && gameMode != Training) {
5147         DisplayMoveError(_("Displayed position is not current"));
5148         return FALSE;
5149     }
5150     return TRUE;
5151 }
5152
5153 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5154 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5155 int lastLoadGameUseList = FALSE;
5156 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5157 ChessMove lastLoadGameStart = (ChessMove) 0;
5158
5159 ChessMove
5160 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5161      int fromX, fromY, toX, toY;
5162      int promoChar;
5163      Boolean captureOwn;
5164 {
5165     ChessMove moveType;
5166     ChessSquare pdown, pup;
5167
5168     /* Check if the user is playing in turn.  This is complicated because we
5169        let the user "pick up" a piece before it is his turn.  So the piece he
5170        tried to pick up may have been captured by the time he puts it down!
5171        Therefore we use the color the user is supposed to be playing in this
5172        test, not the color of the piece that is currently on the starting
5173        square---except in EditGame mode, where the user is playing both
5174        sides; fortunately there the capture race can't happen.  (It can
5175        now happen in IcsExamining mode, but that's just too bad.  The user
5176        will get a somewhat confusing message in that case.)
5177        */
5178
5179     switch (gameMode) {
5180       case PlayFromGameFile:
5181       case AnalyzeFile:
5182       case TwoMachinesPlay:
5183       case EndOfGame:
5184       case IcsObserving:
5185       case IcsIdle:
5186         /* We switched into a game mode where moves are not accepted,
5187            perhaps while the mouse button was down. */
5188         return ImpossibleMove;
5189
5190       case MachinePlaysWhite:
5191         /* User is moving for Black */
5192         if (WhiteOnMove(currentMove)) {
5193             DisplayMoveError(_("It is White's turn"));
5194             return ImpossibleMove;
5195         }
5196         break;
5197
5198       case MachinePlaysBlack:
5199         /* User is moving for White */
5200         if (!WhiteOnMove(currentMove)) {
5201             DisplayMoveError(_("It is Black's turn"));
5202             return ImpossibleMove;
5203         }
5204         break;
5205
5206       case EditGame:
5207       case IcsExamining:
5208       case BeginningOfGame:
5209       case AnalyzeMode:
5210       case Training:
5211         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5212             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5213             /* User is moving for Black */
5214             if (WhiteOnMove(currentMove)) {
5215                 DisplayMoveError(_("It is White's turn"));
5216                 return ImpossibleMove;
5217             }
5218         } else {
5219             /* User is moving for White */
5220             if (!WhiteOnMove(currentMove)) {
5221                 DisplayMoveError(_("It is Black's turn"));
5222                 return ImpossibleMove;
5223             }
5224         }
5225         break;
5226
5227       case IcsPlayingBlack:
5228         /* User is moving for Black */
5229         if (WhiteOnMove(currentMove)) {
5230             if (!appData.premove) {
5231                 DisplayMoveError(_("It is White's turn"));
5232             } else if (toX >= 0 && toY >= 0) {
5233                 premoveToX = toX;
5234                 premoveToY = toY;
5235                 premoveFromX = fromX;
5236                 premoveFromY = fromY;
5237                 premovePromoChar = promoChar;
5238                 gotPremove = 1;
5239                 if (appData.debugMode) 
5240                     fprintf(debugFP, "Got premove: fromX %d,"
5241                             "fromY %d, toX %d, toY %d\n",
5242                             fromX, fromY, toX, toY);
5243             }
5244             return ImpossibleMove;
5245         }
5246         break;
5247
5248       case IcsPlayingWhite:
5249         /* User is moving for White */
5250         if (!WhiteOnMove(currentMove)) {
5251             if (!appData.premove) {
5252                 DisplayMoveError(_("It is Black's turn"));
5253             } else if (toX >= 0 && toY >= 0) {
5254                 premoveToX = toX;
5255                 premoveToY = toY;
5256                 premoveFromX = fromX;
5257                 premoveFromY = fromY;
5258                 premovePromoChar = promoChar;
5259                 gotPremove = 1;
5260                 if (appData.debugMode) 
5261                     fprintf(debugFP, "Got premove: fromX %d,"
5262                             "fromY %d, toX %d, toY %d\n",
5263                             fromX, fromY, toX, toY);
5264             }
5265             return ImpossibleMove;
5266         }
5267         break;
5268
5269       default:
5270         break;
5271
5272       case EditPosition:
5273         /* EditPosition, empty square, or different color piece;
5274            click-click move is possible */
5275         if (toX == -2 || toY == -2) {
5276             boards[0][fromY][fromX] = EmptySquare;
5277             return AmbiguousMove;
5278         } else if (toX >= 0 && toY >= 0) {
5279             boards[0][toY][toX] = boards[0][fromY][fromX];
5280             boards[0][fromY][fromX] = EmptySquare;
5281             return AmbiguousMove;
5282         }
5283         return ImpossibleMove;
5284     }
5285
5286     if(toX < 0 || toY < 0) return ImpossibleMove;
5287     pdown = boards[currentMove][fromY][fromX];
5288     pup = boards[currentMove][toY][toX];
5289
5290     /* [HGM] If move started in holdings, it means a drop */
5291     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5292          if( pup != EmptySquare ) return ImpossibleMove;
5293          if(appData.testLegality) {
5294              /* it would be more logical if LegalityTest() also figured out
5295               * which drops are legal. For now we forbid pawns on back rank.
5296               * Shogi is on its own here...
5297               */
5298              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5299                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5300                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5301          }
5302          return WhiteDrop; /* Not needed to specify white or black yet */
5303     }
5304
5305     userOfferedDraw = FALSE;
5306         
5307     /* [HGM] always test for legality, to get promotion info */
5308     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5309                                          fromY, fromX, toY, toX, promoChar);
5310     /* [HGM] but possibly ignore an IllegalMove result */
5311     if (appData.testLegality) {
5312         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5313             DisplayMoveError(_("Illegal move"));
5314             return ImpossibleMove;
5315         }
5316     }
5317 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5318     return moveType;
5319     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5320        function is made into one that returns an OK move type if FinishMove
5321        should be called. This to give the calling driver routine the
5322        opportunity to finish the userMove input with a promotion popup,
5323        without bothering the user with this for invalid or illegal moves */
5324
5325 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5326 }
5327
5328 /* Common tail of UserMoveEvent and DropMenuEvent */
5329 int
5330 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5331      ChessMove moveType;
5332      int fromX, fromY, toX, toY;
5333      /*char*/int promoChar;
5334 {
5335     char *bookHit = 0;
5336 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5337     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5338         // [HGM] superchess: suppress promotions to non-available piece
5339         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340         if(WhiteOnMove(currentMove)) {
5341             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5342         } else {
5343             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5344         }
5345     }
5346
5347     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5348        move type in caller when we know the move is a legal promotion */
5349     if(moveType == NormalMove && promoChar)
5350         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5351 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5352     /* [HGM] convert drag-and-drop piece drops to standard form */
5353     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5354          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5355            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5356                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5357 //         fromX = boards[currentMove][fromY][fromX];
5358            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5359            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5360            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5361            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5362          fromY = DROP_RANK;
5363     }
5364
5365     /* [HGM] <popupFix> The following if has been moved here from
5366        UserMoveEvent(). Because it seemed to belon here (why not allow
5367        piece drops in training games?), and because it can only be
5368        performed after it is known to what we promote. */
5369     if (gameMode == Training) {
5370       /* compare the move played on the board to the next move in the
5371        * game. If they match, display the move and the opponent's response. 
5372        * If they don't match, display an error message.
5373        */
5374       int saveAnimate;
5375       Board testBoard;
5376       CopyBoard(testBoard, boards[currentMove]);
5377       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5378
5379       if (CompareBoards(testBoard, boards[currentMove+1])) {
5380         ForwardInner(currentMove+1);
5381
5382         /* Autoplay the opponent's response.
5383          * if appData.animate was TRUE when Training mode was entered,
5384          * the response will be animated.
5385          */
5386         saveAnimate = appData.animate;
5387         appData.animate = animateTraining;
5388         ForwardInner(currentMove+1);
5389         appData.animate = saveAnimate;
5390
5391         /* check for the end of the game */
5392         if (currentMove >= forwardMostMove) {
5393           gameMode = PlayFromGameFile;
5394           ModeHighlight();
5395           SetTrainingModeOff();
5396           DisplayInformation(_("End of game"));
5397         }
5398       } else {
5399         DisplayError(_("Incorrect move"), 0);
5400       }
5401       return 1;
5402     }
5403
5404   /* Ok, now we know that the move is good, so we can kill
5405      the previous line in Analysis Mode */
5406   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5407                                 && currentMove < forwardMostMove) {
5408     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5409   }
5410
5411   /* If we need the chess program but it's dead, restart it */
5412   ResurrectChessProgram();
5413
5414   /* A user move restarts a paused game*/
5415   if (pausing)
5416     PauseEvent();
5417
5418   thinkOutput[0] = NULLCHAR;
5419
5420   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5421
5422   if (gameMode == BeginningOfGame) {
5423     if (appData.noChessProgram) {
5424       gameMode = EditGame;
5425       SetGameInfo();
5426     } else {
5427       char buf[MSG_SIZ];
5428       gameMode = MachinePlaysBlack;
5429       StartClocks();
5430       SetGameInfo();
5431       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5432       DisplayTitle(buf);
5433       if (first.sendName) {
5434         sprintf(buf, "name %s\n", gameInfo.white);
5435         SendToProgram(buf, &first);
5436       }
5437       StartClocks();
5438     }
5439     ModeHighlight();
5440   }
5441 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5442   /* Relay move to ICS or chess engine */
5443   if (appData.icsActive) {
5444     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5445         gameMode == IcsExamining) {
5446       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5447       ics_user_moved = 1;
5448     }
5449   } else {
5450     if (first.sendTime && (gameMode == BeginningOfGame ||
5451                            gameMode == MachinePlaysWhite ||
5452                            gameMode == MachinePlaysBlack)) {
5453       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5454     }
5455     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5456          // [HGM] book: if program might be playing, let it use book
5457         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5458         first.maybeThinking = TRUE;
5459     } else SendMoveToProgram(forwardMostMove-1, &first);
5460     if (currentMove == cmailOldMove + 1) {
5461       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5462     }
5463   }
5464
5465   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5466
5467   switch (gameMode) {
5468   case EditGame:
5469     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5470     case MT_NONE:
5471     case MT_CHECK:
5472       break;
5473     case MT_CHECKMATE:
5474     case MT_STAINMATE:
5475       if (WhiteOnMove(currentMove)) {
5476         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5477       } else {
5478         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5479       }
5480       break;
5481     case MT_STALEMATE:
5482       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5483       break;
5484     }
5485     break;
5486     
5487   case MachinePlaysBlack:
5488   case MachinePlaysWhite:
5489     /* disable certain menu options while machine is thinking */
5490     SetMachineThinkingEnables();
5491     break;
5492
5493   default:
5494     break;
5495   }
5496
5497   if(bookHit) { // [HGM] book: simulate book reply
5498         static char bookMove[MSG_SIZ]; // a bit generous?
5499
5500         programStats.nodes = programStats.depth = programStats.time = 
5501         programStats.score = programStats.got_only_move = 0;
5502         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5503
5504         strcpy(bookMove, "move ");
5505         strcat(bookMove, bookHit);
5506         HandleMachineMove(bookMove, &first);
5507   }
5508   return 1;
5509 }
5510
5511 void
5512 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5513      int fromX, fromY, toX, toY;
5514      int promoChar;
5515 {
5516     /* [HGM] This routine was added to allow calling of its two logical
5517        parts from other modules in the old way. Before, UserMoveEvent()
5518        automatically called FinishMove() if the move was OK, and returned
5519        otherwise. I separated the two, in order to make it possible to
5520        slip a promotion popup in between. But that it always needs two
5521        calls, to the first part, (now called UserMoveTest() ), and to
5522        FinishMove if the first part succeeded. Calls that do not need
5523        to do anything in between, can call this routine the old way. 
5524     */
5525     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5526 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5527     if(moveType == AmbiguousMove)
5528         DrawPosition(FALSE, boards[currentMove]);
5529     else if(moveType != ImpossibleMove && moveType != Comment)
5530         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5531 }
5532
5533 void LeftClick(ClickType clickType, int xPix, int yPix)
5534 {
5535     int x, y;
5536     Boolean saveAnimate;
5537     static int second = 0, promotionChoice = 0;
5538     char promoChoice = NULLCHAR;
5539
5540     if (clickType == Press) ErrorPopDown();
5541
5542     x = EventToSquare(xPix, BOARD_WIDTH);
5543     y = EventToSquare(yPix, BOARD_HEIGHT);
5544     if (!flipView && y >= 0) {
5545         y = BOARD_HEIGHT - 1 - y;
5546     }
5547     if (flipView && x >= 0) {
5548         x = BOARD_WIDTH - 1 - x;
5549     }
5550
5551     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5552         if(clickType == Release) return; // ignore upclick of click-click destination
5553         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5554         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5555         if(gameInfo.holdingsWidth && 
5556                 (WhiteOnMove(currentMove) 
5557                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5558                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5559             // click in right holdings, for determining promotion piece
5560             ChessSquare p = boards[currentMove][y][x];
5561             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5562             if(p != EmptySquare) {
5563                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5564                 fromX = fromY = -1;
5565                 return;
5566             }
5567         }
5568         DrawPosition(FALSE, boards[currentMove]);
5569         return;
5570     }
5571
5572     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5573     if(clickType == Press
5574             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5575               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5576               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5577         return;
5578
5579     if (fromX == -1) {
5580         if (clickType == Press) {
5581             /* First square */
5582             if (OKToStartUserMove(x, y)) {
5583                 fromX = x;
5584                 fromY = y;
5585                 second = 0;
5586                 DragPieceBegin(xPix, yPix);
5587                 if (appData.highlightDragging) {
5588                     SetHighlights(x, y, -1, -1);
5589                 }
5590             }
5591         }
5592         return;
5593     }
5594
5595     /* fromX != -1 */
5596     if (clickType == Press && gameMode != EditPosition) {
5597         ChessSquare fromP;
5598         ChessSquare toP;
5599         int frc;
5600
5601         // ignore off-board to clicks
5602         if(y < 0 || x < 0) return;
5603
5604         /* Check if clicking again on the same color piece */
5605         fromP = boards[currentMove][fromY][fromX];
5606         toP = boards[currentMove][y][x];
5607         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5608         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5609              WhitePawn <= toP && toP <= WhiteKing &&
5610              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5611              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5612             (BlackPawn <= fromP && fromP <= BlackKing && 
5613              BlackPawn <= toP && toP <= BlackKing &&
5614              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5615              !(fromP == BlackKing && toP == BlackRook && frc))) {
5616             /* Clicked again on same color piece -- changed his mind */
5617             second = (x == fromX && y == fromY);
5618             if (appData.highlightDragging) {
5619                 SetHighlights(x, y, -1, -1);
5620             } else {
5621                 ClearHighlights();
5622             }
5623             if (OKToStartUserMove(x, y)) {
5624                 fromX = x;
5625                 fromY = y;
5626                 DragPieceBegin(xPix, yPix);
5627             }
5628             return;
5629         }
5630         // ignore clicks on holdings
5631         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5632     }
5633
5634     if (clickType == Release && x == fromX && y == fromY) {
5635         DragPieceEnd(xPix, yPix);
5636         if (appData.animateDragging) {
5637             /* Undo animation damage if any */
5638             DrawPosition(FALSE, NULL);
5639         }
5640         if (second) {
5641             /* Second up/down in same square; just abort move */
5642             second = 0;
5643             fromX = fromY = -1;
5644             ClearHighlights();
5645             gotPremove = 0;
5646             ClearPremoveHighlights();
5647         } else {
5648             /* First upclick in same square; start click-click mode */
5649             SetHighlights(x, y, -1, -1);
5650         }
5651         return;
5652     }
5653
5654     /* we now have a different from- and (possibly off-board) to-square */
5655     /* Completed move */
5656     toX = x;
5657     toY = y;
5658     saveAnimate = appData.animate;
5659     if (clickType == Press) {
5660         /* Finish clickclick move */
5661         if (appData.animate || appData.highlightLastMove) {
5662             SetHighlights(fromX, fromY, toX, toY);
5663         } else {
5664             ClearHighlights();
5665         }
5666     } else {
5667         /* Finish drag move */
5668         if (appData.highlightLastMove) {
5669             SetHighlights(fromX, fromY, toX, toY);
5670         } else {
5671             ClearHighlights();
5672         }
5673         DragPieceEnd(xPix, yPix);
5674         /* Don't animate move and drag both */
5675         appData.animate = FALSE;
5676     }
5677
5678     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5679     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5680         ClearHighlights();
5681         fromX = fromY = -1;
5682         DrawPosition(TRUE, NULL);
5683         return;
5684     }
5685
5686     // off-board moves should not be highlighted
5687     if(x < 0 || x < 0) ClearHighlights();
5688
5689     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5690         SetHighlights(fromX, fromY, toX, toY);
5691         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5692             // [HGM] super: promotion to captured piece selected from holdings
5693             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5694             promotionChoice = TRUE;
5695             // kludge follows to temporarily execute move on display, without promoting yet
5696             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5697             boards[currentMove][toY][toX] = p;
5698             DrawPosition(FALSE, boards[currentMove]);
5699             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5700             boards[currentMove][toY][toX] = q;
5701             DisplayMessage("Click in holdings to choose piece", "");
5702             return;
5703         }
5704         PromotionPopUp();
5705     } else {
5706         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5707         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5708         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5709         fromX = fromY = -1;
5710     }
5711     appData.animate = saveAnimate;
5712     if (appData.animate || appData.animateDragging) {
5713         /* Undo animation damage if needed */
5714         DrawPosition(FALSE, NULL);
5715     }
5716 }
5717
5718 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5719 {
5720 //    char * hint = lastHint;
5721     FrontEndProgramStats stats;
5722
5723     stats.which = cps == &first ? 0 : 1;
5724     stats.depth = cpstats->depth;
5725     stats.nodes = cpstats->nodes;
5726     stats.score = cpstats->score;
5727     stats.time = cpstats->time;
5728     stats.pv = cpstats->movelist;
5729     stats.hint = lastHint;
5730     stats.an_move_index = 0;
5731     stats.an_move_count = 0;
5732
5733     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5734         stats.hint = cpstats->move_name;
5735         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5736         stats.an_move_count = cpstats->nr_moves;
5737     }
5738
5739     SetProgramStats( &stats );
5740 }
5741
5742 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5743 {   // [HGM] book: this routine intercepts moves to simulate book replies
5744     char *bookHit = NULL;
5745
5746     //first determine if the incoming move brings opponent into his book
5747     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5748         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5749     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5750     if(bookHit != NULL && !cps->bookSuspend) {
5751         // make sure opponent is not going to reply after receiving move to book position
5752         SendToProgram("force\n", cps);
5753         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5754     }
5755     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5756     // now arrange restart after book miss
5757     if(bookHit) {
5758         // after a book hit we never send 'go', and the code after the call to this routine
5759         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5760         char buf[MSG_SIZ];
5761         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5762         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5763         SendToProgram(buf, cps);
5764         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5765     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5766         SendToProgram("go\n", cps);
5767         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5768     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5769         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5770             SendToProgram("go\n", cps); 
5771         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5772     }
5773     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5774 }
5775
5776 char *savedMessage;
5777 ChessProgramState *savedState;
5778 void DeferredBookMove(void)
5779 {
5780         if(savedState->lastPing != savedState->lastPong)
5781                     ScheduleDelayedEvent(DeferredBookMove, 10);
5782         else
5783         HandleMachineMove(savedMessage, savedState);
5784 }
5785
5786 void
5787 HandleMachineMove(message, cps)
5788      char *message;
5789      ChessProgramState *cps;
5790 {
5791     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5792     char realname[MSG_SIZ];
5793     int fromX, fromY, toX, toY;
5794     ChessMove moveType;
5795     char promoChar;
5796     char *p;
5797     int machineWhite;
5798     char *bookHit;
5799
5800 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5801     /*
5802      * Kludge to ignore BEL characters
5803      */
5804     while (*message == '\007') message++;
5805
5806     /*
5807      * [HGM] engine debug message: ignore lines starting with '#' character
5808      */
5809     if(cps->debug && *message == '#') return;
5810
5811     /*
5812      * Look for book output
5813      */
5814     if (cps == &first && bookRequested) {
5815         if (message[0] == '\t' || message[0] == ' ') {
5816             /* Part of the book output is here; append it */
5817             strcat(bookOutput, message);
5818             strcat(bookOutput, "  \n");
5819             return;
5820         } else if (bookOutput[0] != NULLCHAR) {
5821             /* All of book output has arrived; display it */
5822             char *p = bookOutput;
5823             while (*p != NULLCHAR) {
5824                 if (*p == '\t') *p = ' ';
5825                 p++;
5826             }
5827             DisplayInformation(bookOutput);
5828             bookRequested = FALSE;
5829             /* Fall through to parse the current output */
5830         }
5831     }
5832
5833     /*
5834      * Look for machine move.
5835      */
5836     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5837         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5838     {
5839         /* This method is only useful on engines that support ping */
5840         if (cps->lastPing != cps->lastPong) {
5841           if (gameMode == BeginningOfGame) {
5842             /* Extra move from before last new; ignore */
5843             if (appData.debugMode) {
5844                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5845             }
5846           } else {
5847             if (appData.debugMode) {
5848                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5849                         cps->which, gameMode);
5850             }
5851
5852             SendToProgram("undo\n", cps);
5853           }
5854           return;
5855         }
5856
5857         switch (gameMode) {
5858           case BeginningOfGame:
5859             /* Extra move from before last reset; ignore */
5860             if (appData.debugMode) {
5861                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5862             }
5863             return;
5864
5865           case EndOfGame:
5866           case IcsIdle:
5867           default:
5868             /* Extra move after we tried to stop.  The mode test is
5869                not a reliable way of detecting this problem, but it's
5870                the best we can do on engines that don't support ping.
5871             */
5872             if (appData.debugMode) {
5873                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5874                         cps->which, gameMode);
5875             }
5876             SendToProgram("undo\n", cps);
5877             return;
5878
5879           case MachinePlaysWhite:
5880           case IcsPlayingWhite:
5881             machineWhite = TRUE;
5882             break;
5883
5884           case MachinePlaysBlack:
5885           case IcsPlayingBlack:
5886             machineWhite = FALSE;
5887             break;
5888
5889           case TwoMachinesPlay:
5890             machineWhite = (cps->twoMachinesColor[0] == 'w');
5891             break;
5892         }
5893         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5894             if (appData.debugMode) {
5895                 fprintf(debugFP,
5896                         "Ignoring move out of turn by %s, gameMode %d"
5897                         ", forwardMost %d\n",
5898                         cps->which, gameMode, forwardMostMove);
5899             }
5900             return;
5901         }
5902
5903     if (appData.debugMode) { int f = forwardMostMove;
5904         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5905                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5906                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5907     }
5908         if(cps->alphaRank) AlphaRank(machineMove, 4);
5909         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5910                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5911             /* Machine move could not be parsed; ignore it. */
5912             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5913                     machineMove, cps->which);
5914             DisplayError(buf1, 0);
5915             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5916                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5917             if (gameMode == TwoMachinesPlay) {
5918               GameEnds(machineWhite ? BlackWins : WhiteWins,
5919                        buf1, GE_XBOARD);
5920             }
5921             return;
5922         }
5923
5924         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5925         /* So we have to redo legality test with true e.p. status here,  */
5926         /* to make sure an illegal e.p. capture does not slip through,   */
5927         /* to cause a forfeit on a justified illegal-move complaint      */
5928         /* of the opponent.                                              */
5929         if( gameMode==TwoMachinesPlay && appData.testLegality
5930             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5931                                                               ) {
5932            ChessMove moveType;
5933            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5934                              fromY, fromX, toY, toX, promoChar);
5935             if (appData.debugMode) {
5936                 int i;
5937                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5938                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5939                 fprintf(debugFP, "castling rights\n");
5940             }
5941             if(moveType == IllegalMove) {
5942                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5943                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5944                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5945                            buf1, GE_XBOARD);
5946                 return;
5947            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5948            /* [HGM] Kludge to handle engines that send FRC-style castling
5949               when they shouldn't (like TSCP-Gothic) */
5950            switch(moveType) {
5951              case WhiteASideCastleFR:
5952              case BlackASideCastleFR:
5953                toX+=2;
5954                currentMoveString[2]++;
5955                break;
5956              case WhiteHSideCastleFR:
5957              case BlackHSideCastleFR:
5958                toX--;
5959                currentMoveString[2]--;
5960                break;
5961              default: ; // nothing to do, but suppresses warning of pedantic compilers
5962            }
5963         }
5964         hintRequested = FALSE;
5965         lastHint[0] = NULLCHAR;
5966         bookRequested = FALSE;
5967         /* Program may be pondering now */
5968         cps->maybeThinking = TRUE;
5969         if (cps->sendTime == 2) cps->sendTime = 1;
5970         if (cps->offeredDraw) cps->offeredDraw--;
5971
5972 #if ZIPPY
5973         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5974             first.initDone) {
5975           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5976           ics_user_moved = 1;
5977           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5978                 char buf[3*MSG_SIZ];
5979
5980                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5981                         programStats.score / 100.,
5982                         programStats.depth,
5983                         programStats.time / 100.,
5984                         (unsigned int)programStats.nodes,
5985                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5986                         programStats.movelist);
5987                 SendToICS(buf);
5988 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5989           }
5990         }
5991 #endif
5992         /* currentMoveString is set as a side-effect of ParseOneMove */
5993         strcpy(machineMove, currentMoveString);
5994         strcat(machineMove, "\n");
5995         strcpy(moveList[forwardMostMove], machineMove);
5996
5997         /* [AS] Save move info and clear stats for next move */
5998         pvInfoList[ forwardMostMove ].score = programStats.score;
5999         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6000         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6001         ClearProgramStats();
6002         thinkOutput[0] = NULLCHAR;
6003         hiddenThinkOutputState = 0;
6004
6005         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6006
6007         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6008         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6009             int count = 0;
6010
6011             while( count < adjudicateLossPlies ) {
6012                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6013
6014                 if( count & 1 ) {
6015                     score = -score; /* Flip score for winning side */
6016                 }
6017
6018                 if( score > adjudicateLossThreshold ) {
6019                     break;
6020                 }
6021
6022                 count++;
6023             }
6024
6025             if( count >= adjudicateLossPlies ) {
6026                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6027
6028                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6029                     "Xboard adjudication", 
6030                     GE_XBOARD );
6031
6032                 return;
6033             }
6034         }
6035
6036         if( gameMode == TwoMachinesPlay ) {
6037           // [HGM] some adjudications useful with buggy engines
6038             int k, count = 0; static int bare = 1;
6039           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6040
6041
6042             if( appData.testLegality )
6043             {   /* [HGM] Some more adjudications for obstinate engines */
6044                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6045                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6046                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6047                 static int moveCount = 6;
6048                 ChessMove result;
6049                 char *reason = NULL;
6050
6051                 /* Count what is on board. */
6052                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6053                 {   ChessSquare p = boards[forwardMostMove][i][j];
6054                     int m=i;
6055
6056                     switch((int) p)
6057                     {   /* count B,N,R and other of each side */
6058                         case WhiteKing:
6059                         case BlackKing:
6060                              NrK++; break; // [HGM] atomic: count Kings
6061                         case WhiteKnight:
6062                              NrWN++; break;
6063                         case WhiteBishop:
6064                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6065                              bishopsColor |= 1 << ((i^j)&1);
6066                              NrWB++; break;
6067                         case BlackKnight:
6068                              NrBN++; break;
6069                         case BlackBishop:
6070                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6071                              bishopsColor |= 1 << ((i^j)&1);
6072                              NrBB++; break;
6073                         case WhiteRook:
6074                              NrWR++; break;
6075                         case BlackRook:
6076                              NrBR++; break;
6077                         case WhiteQueen:
6078                              NrWQ++; break;
6079                         case BlackQueen:
6080                              NrBQ++; break;
6081                         case EmptySquare: 
6082                              break;
6083                         case BlackPawn:
6084                              m = 7-i;
6085                         case WhitePawn:
6086                              PawnAdvance += m; NrPawns++;
6087                     }
6088                     NrPieces += (p != EmptySquare);
6089                     NrW += ((int)p < (int)BlackPawn);
6090                     if(gameInfo.variant == VariantXiangqi && 
6091                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6092                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6093                         NrW -= ((int)p < (int)BlackPawn);
6094                     }
6095                 }
6096
6097                 /* Some material-based adjudications that have to be made before stalemate test */
6098                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6099                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6100                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6101                      if(appData.checkMates) {
6102                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6103                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6104                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6105                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6106                          return;
6107                      }
6108                 }
6109
6110                 /* Bare King in Shatranj (loses) or Losers (wins) */
6111                 if( NrW == 1 || NrPieces - NrW == 1) {
6112                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6113                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6114                      if(appData.checkMates) {
6115                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6116                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6117                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6118                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6119                          return;
6120                      }
6121                   } else
6122                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6123                   {    /* bare King */
6124                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6125                         if(appData.checkMates) {
6126                             /* but only adjudicate if adjudication enabled */
6127                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6128                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6129                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6130                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6131                             return;
6132                         }
6133                   }
6134                 } else bare = 1;
6135
6136
6137             // don't wait for engine to announce game end if we can judge ourselves
6138             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6139               case MT_CHECK:
6140                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6141                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6142                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6143                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6144                             checkCnt++;
6145                         if(checkCnt >= 2) {
6146                             reason = "Xboard adjudication: 3rd check";
6147                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6148                             break;
6149                         }
6150                     }
6151                 }
6152               case MT_NONE:
6153               default:
6154                 break;
6155               case MT_STALEMATE:
6156               case MT_STAINMATE:
6157                 reason = "Xboard adjudication: Stalemate";
6158                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6159                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6160                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6161                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6162                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6163                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6164                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6165                                                                         EP_CHECKMATE : EP_WINS);
6166                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6167                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6168                 }
6169                 break;
6170               case MT_CHECKMATE:
6171                 reason = "Xboard adjudication: Checkmate";
6172                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6173                 break;
6174             }
6175
6176                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6177                     case EP_STALEMATE:
6178                         result = GameIsDrawn; break;
6179                     case EP_CHECKMATE:
6180                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6181                     case EP_WINS:
6182                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6183                     default:
6184                         result = (ChessMove) 0;
6185                 }
6186                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6187                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6188                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6189                     GameEnds( result, reason, GE_XBOARD );
6190                     return;
6191                 }
6192
6193                 /* Next absolutely insufficient mating material. */
6194                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6195                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6196                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6197                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6198                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6199
6200                      /* always flag draws, for judging claims */
6201                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6202
6203                      if(appData.materialDraws) {
6204                          /* but only adjudicate them if adjudication enabled */
6205                          SendToProgram("force\n", cps->other); // suppress reply
6206                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6207                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6208                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6209                          return;
6210                      }
6211                 }
6212
6213                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6214                 if(NrPieces == 4 && 
6215                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6216                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6217                    || NrWN==2 || NrBN==2     /* KNNK */
6218                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6219                   ) ) {
6220                      if(--moveCount < 0 && appData.trivialDraws)
6221                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6222                           SendToProgram("force\n", cps->other); // suppress reply
6223                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6224                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6225                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6226                           return;
6227                      }
6228                 } else moveCount = 6;
6229             }
6230           }
6231           
6232           if (appData.debugMode) { int i;
6233             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6234                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6235                     appData.drawRepeats);
6236             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6237               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6238             
6239           }
6240
6241                 /* Check for rep-draws */
6242                 count = 0;
6243                 for(k = forwardMostMove-2;
6244                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6245                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6246                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6247                     k-=2)
6248                 {   int rights=0;
6249                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6250                         /* compare castling rights */
6251                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6252                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6253                                 rights++; /* King lost rights, while rook still had them */
6254                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6255                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6256                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6257                                    rights++; /* but at least one rook lost them */
6258                         }
6259                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6260                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6261                                 rights++; 
6262                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6263                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6264                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6265                                    rights++;
6266                         }
6267                         if( rights == 0 && ++count > appData.drawRepeats-2
6268                             && appData.drawRepeats > 1) {
6269                              /* adjudicate after user-specified nr of repeats */
6270                              SendToProgram("force\n", cps->other); // suppress reply
6271                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6272                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6273                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6274                                 // [HGM] xiangqi: check for forbidden perpetuals
6275                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6276                                 for(m=forwardMostMove; m>k; m-=2) {
6277                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6278                                         ourPerpetual = 0; // the current mover did not always check
6279                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6280                                         hisPerpetual = 0; // the opponent did not always check
6281                                 }
6282                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6283                                                                         ourPerpetual, hisPerpetual);
6284                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6285                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6286                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6287                                     return;
6288                                 }
6289                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6290                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6291                                 // Now check for perpetual chases
6292                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6293                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6294                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6295                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6296                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6297                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6298                                         return;
6299                                     }
6300                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6301                                         break; // Abort repetition-checking loop.
6302                                 }
6303                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6304                              }
6305                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6306                              return;
6307                         }
6308                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6309                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6310                     }
6311                 }
6312
6313                 /* Now we test for 50-move draws. Determine ply count */
6314                 count = forwardMostMove;
6315                 /* look for last irreversble move */
6316                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6317                     count--;
6318                 /* if we hit starting position, add initial plies */
6319                 if( count == backwardMostMove )
6320                     count -= initialRulePlies;
6321                 count = forwardMostMove - count; 
6322                 if( count >= 100)
6323                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6324                          /* this is used to judge if draw claims are legal */
6325                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6326                          SendToProgram("force\n", cps->other); // suppress reply
6327                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6328                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6329                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6330                          return;
6331                 }
6332
6333                 /* if draw offer is pending, treat it as a draw claim
6334                  * when draw condition present, to allow engines a way to
6335                  * claim draws before making their move to avoid a race
6336                  * condition occurring after their move
6337                  */
6338                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6339                          char *p = NULL;
6340                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6341                              p = "Draw claim: 50-move rule";
6342                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6343                              p = "Draw claim: 3-fold repetition";
6344                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6345                              p = "Draw claim: insufficient mating material";
6346                          if( p != NULL ) {
6347                              SendToProgram("force\n", cps->other); // suppress reply
6348                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6349                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6350                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6351                              return;
6352                          }
6353                 }
6354
6355
6356                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6357                     SendToProgram("force\n", cps->other); // suppress reply
6358                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6359                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6360
6361                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6362
6363                     return;
6364                 }
6365         }
6366
6367         bookHit = NULL;
6368         if (gameMode == TwoMachinesPlay) {
6369             /* [HGM] relaying draw offers moved to after reception of move */
6370             /* and interpreting offer as claim if it brings draw condition */
6371             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6372                 SendToProgram("draw\n", cps->other);
6373             }
6374             if (cps->other->sendTime) {
6375                 SendTimeRemaining(cps->other,
6376                                   cps->other->twoMachinesColor[0] == 'w');
6377             }
6378             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6379             if (firstMove && !bookHit) {
6380                 firstMove = FALSE;
6381                 if (cps->other->useColors) {
6382                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6383                 }
6384                 SendToProgram("go\n", cps->other);
6385             }
6386             cps->other->maybeThinking = TRUE;
6387         }
6388
6389         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6390         
6391         if (!pausing && appData.ringBellAfterMoves) {
6392             RingBell();
6393         }
6394
6395         /* 
6396          * Reenable menu items that were disabled while
6397          * machine was thinking
6398          */
6399         if (gameMode != TwoMachinesPlay)
6400             SetUserThinkingEnables();
6401
6402         // [HGM] book: after book hit opponent has received move and is now in force mode
6403         // force the book reply into it, and then fake that it outputted this move by jumping
6404         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6405         if(bookHit) {
6406                 static char bookMove[MSG_SIZ]; // a bit generous?
6407
6408                 strcpy(bookMove, "move ");
6409                 strcat(bookMove, bookHit);
6410                 message = bookMove;
6411                 cps = cps->other;
6412                 programStats.nodes = programStats.depth = programStats.time = 
6413                 programStats.score = programStats.got_only_move = 0;
6414                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6415
6416                 if(cps->lastPing != cps->lastPong) {
6417                     savedMessage = message; // args for deferred call
6418                     savedState = cps;
6419                     ScheduleDelayedEvent(DeferredBookMove, 10);
6420                     return;
6421                 }
6422                 goto FakeBookMove;
6423         }
6424
6425         return;
6426     }
6427
6428     /* Set special modes for chess engines.  Later something general
6429      *  could be added here; for now there is just one kludge feature,
6430      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6431      *  when "xboard" is given as an interactive command.
6432      */
6433     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6434         cps->useSigint = FALSE;
6435         cps->useSigterm = FALSE;
6436     }
6437     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6438       ParseFeatures(message+8, cps);
6439       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6440     }
6441
6442     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6443      * want this, I was asked to put it in, and obliged.
6444      */
6445     if (!strncmp(message, "setboard ", 9)) {
6446         Board initial_position;
6447
6448         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6449
6450         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6451             DisplayError(_("Bad FEN received from engine"), 0);
6452             return ;
6453         } else {
6454            Reset(TRUE, FALSE);
6455            CopyBoard(boards[0], initial_position);
6456            initialRulePlies = FENrulePlies;
6457            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6458            else gameMode = MachinePlaysBlack;                 
6459            DrawPosition(FALSE, boards[currentMove]);
6460         }
6461         return;
6462     }
6463
6464     /*
6465      * Look for communication commands
6466      */
6467     if (!strncmp(message, "telluser ", 9)) {
6468         DisplayNote(message + 9);
6469         return;
6470     }
6471     if (!strncmp(message, "tellusererror ", 14)) {
6472         DisplayError(message + 14, 0);
6473         return;
6474     }
6475     if (!strncmp(message, "tellopponent ", 13)) {
6476       if (appData.icsActive) {
6477         if (loggedOn) {
6478           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6479           SendToICS(buf1);
6480         }
6481       } else {
6482         DisplayNote(message + 13);
6483       }
6484       return;
6485     }
6486     if (!strncmp(message, "tellothers ", 11)) {
6487       if (appData.icsActive) {
6488         if (loggedOn) {
6489           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6490           SendToICS(buf1);
6491         }
6492       }
6493       return;
6494     }
6495     if (!strncmp(message, "tellall ", 8)) {
6496       if (appData.icsActive) {
6497         if (loggedOn) {
6498           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6499           SendToICS(buf1);
6500         }
6501       } else {
6502         DisplayNote(message + 8);
6503       }
6504       return;
6505     }
6506     if (strncmp(message, "warning", 7) == 0) {
6507         /* Undocumented feature, use tellusererror in new code */
6508         DisplayError(message, 0);
6509         return;
6510     }
6511     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6512         strcpy(realname, cps->tidy);
6513         strcat(realname, " query");
6514         AskQuestion(realname, buf2, buf1, cps->pr);
6515         return;
6516     }
6517     /* Commands from the engine directly to ICS.  We don't allow these to be 
6518      *  sent until we are logged on. Crafty kibitzes have been known to 
6519      *  interfere with the login process.
6520      */
6521     if (loggedOn) {
6522         if (!strncmp(message, "tellics ", 8)) {
6523             SendToICS(message + 8);
6524             SendToICS("\n");
6525             return;
6526         }
6527         if (!strncmp(message, "tellicsnoalias ", 15)) {
6528             SendToICS(ics_prefix);
6529             SendToICS(message + 15);
6530             SendToICS("\n");
6531             return;
6532         }
6533         /* The following are for backward compatibility only */
6534         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6535             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6536             SendToICS(ics_prefix);
6537             SendToICS(message);
6538             SendToICS("\n");
6539             return;
6540         }
6541     }
6542     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6543         return;
6544     }
6545     /*
6546      * If the move is illegal, cancel it and redraw the board.
6547      * Also deal with other error cases.  Matching is rather loose
6548      * here to accommodate engines written before the spec.
6549      */
6550     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6551         strncmp(message, "Error", 5) == 0) {
6552         if (StrStr(message, "name") || 
6553             StrStr(message, "rating") || StrStr(message, "?") ||
6554             StrStr(message, "result") || StrStr(message, "board") ||
6555             StrStr(message, "bk") || StrStr(message, "computer") ||
6556             StrStr(message, "variant") || StrStr(message, "hint") ||
6557             StrStr(message, "random") || StrStr(message, "depth") ||
6558             StrStr(message, "accepted")) {
6559             return;
6560         }
6561         if (StrStr(message, "protover")) {
6562           /* Program is responding to input, so it's apparently done
6563              initializing, and this error message indicates it is
6564              protocol version 1.  So we don't need to wait any longer
6565              for it to initialize and send feature commands. */
6566           FeatureDone(cps, 1);
6567           cps->protocolVersion = 1;
6568           return;
6569         }
6570         cps->maybeThinking = FALSE;
6571
6572         if (StrStr(message, "draw")) {
6573             /* Program doesn't have "draw" command */
6574             cps->sendDrawOffers = 0;
6575             return;
6576         }
6577         if (cps->sendTime != 1 &&
6578             (StrStr(message, "time") || StrStr(message, "otim"))) {
6579           /* Program apparently doesn't have "time" or "otim" command */
6580           cps->sendTime = 0;
6581           return;
6582         }
6583         if (StrStr(message, "analyze")) {
6584             cps->analysisSupport = FALSE;
6585             cps->analyzing = FALSE;
6586             Reset(FALSE, TRUE);
6587             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6588             DisplayError(buf2, 0);
6589             return;
6590         }
6591         if (StrStr(message, "(no matching move)st")) {
6592           /* Special kludge for GNU Chess 4 only */
6593           cps->stKludge = TRUE;
6594           SendTimeControl(cps, movesPerSession, timeControl,
6595                           timeIncrement, appData.searchDepth,
6596                           searchTime);
6597           return;
6598         }
6599         if (StrStr(message, "(no matching move)sd")) {
6600           /* Special kludge for GNU Chess 4 only */
6601           cps->sdKludge = TRUE;
6602           SendTimeControl(cps, movesPerSession, timeControl,
6603                           timeIncrement, appData.searchDepth,
6604                           searchTime);
6605           return;
6606         }
6607         if (!StrStr(message, "llegal")) {
6608             return;
6609         }
6610         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6611             gameMode == IcsIdle) return;
6612         if (forwardMostMove <= backwardMostMove) return;
6613         if (pausing) PauseEvent();
6614       if(appData.forceIllegal) {
6615             // [HGM] illegal: machine refused move; force position after move into it
6616           SendToProgram("force\n", cps);
6617           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6618                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6619                 // when black is to move, while there might be nothing on a2 or black
6620                 // might already have the move. So send the board as if white has the move.
6621                 // But first we must change the stm of the engine, as it refused the last move
6622                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6623                 if(WhiteOnMove(forwardMostMove)) {
6624                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6625                     SendBoard(cps, forwardMostMove); // kludgeless board
6626                 } else {
6627                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6628                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6629                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6630                 }
6631           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6632             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6633                  gameMode == TwoMachinesPlay)
6634               SendToProgram("go\n", cps);
6635             return;
6636       } else
6637         if (gameMode == PlayFromGameFile) {
6638             /* Stop reading this game file */
6639             gameMode = EditGame;
6640             ModeHighlight();
6641         }
6642         currentMove = --forwardMostMove;
6643         DisplayMove(currentMove-1); /* before DisplayMoveError */
6644         SwitchClocks();
6645         DisplayBothClocks();
6646         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6647                 parseList[currentMove], cps->which);
6648         DisplayMoveError(buf1);
6649         DrawPosition(FALSE, boards[currentMove]);
6650
6651         /* [HGM] illegal-move claim should forfeit game when Xboard */
6652         /* only passes fully legal moves                            */
6653         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6654             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6655                                 "False illegal-move claim", GE_XBOARD );
6656         }
6657         return;
6658     }
6659     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6660         /* Program has a broken "time" command that
6661            outputs a string not ending in newline.
6662            Don't use it. */
6663         cps->sendTime = 0;
6664     }
6665     
6666     /*
6667      * If chess program startup fails, exit with an error message.
6668      * Attempts to recover here are futile.
6669      */
6670     if ((StrStr(message, "unknown host") != NULL)
6671         || (StrStr(message, "No remote directory") != NULL)
6672         || (StrStr(message, "not found") != NULL)
6673         || (StrStr(message, "No such file") != NULL)
6674         || (StrStr(message, "can't alloc") != NULL)
6675         || (StrStr(message, "Permission denied") != NULL)) {
6676
6677         cps->maybeThinking = FALSE;
6678         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6679                 cps->which, cps->program, cps->host, message);
6680         RemoveInputSource(cps->isr);
6681         DisplayFatalError(buf1, 0, 1);
6682         return;
6683     }
6684     
6685     /* 
6686      * Look for hint output
6687      */
6688     if (sscanf(message, "Hint: %s", buf1) == 1) {
6689         if (cps == &first && hintRequested) {
6690             hintRequested = FALSE;
6691             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6692                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6693                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6694                                     PosFlags(forwardMostMove),
6695                                     fromY, fromX, toY, toX, promoChar, buf1);
6696                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6697                 DisplayInformation(buf2);
6698             } else {
6699                 /* Hint move could not be parsed!? */
6700               snprintf(buf2, sizeof(buf2),
6701                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6702                         buf1, cps->which);
6703                 DisplayError(buf2, 0);
6704             }
6705         } else {
6706             strcpy(lastHint, buf1);
6707         }
6708         return;
6709     }
6710
6711     /*
6712      * Ignore other messages if game is not in progress
6713      */
6714     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6715         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6716
6717     /*
6718      * look for win, lose, draw, or draw offer
6719      */
6720     if (strncmp(message, "1-0", 3) == 0) {
6721         char *p, *q, *r = "";
6722         p = strchr(message, '{');
6723         if (p) {
6724             q = strchr(p, '}');
6725             if (q) {
6726                 *q = NULLCHAR;
6727                 r = p + 1;
6728             }
6729         }
6730         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6731         return;
6732     } else if (strncmp(message, "0-1", 3) == 0) {
6733         char *p, *q, *r = "";
6734         p = strchr(message, '{');
6735         if (p) {
6736             q = strchr(p, '}');
6737             if (q) {
6738                 *q = NULLCHAR;
6739                 r = p + 1;
6740             }
6741         }
6742         /* Kludge for Arasan 4.1 bug */
6743         if (strcmp(r, "Black resigns") == 0) {
6744             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6745             return;
6746         }
6747         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6748         return;
6749     } else if (strncmp(message, "1/2", 3) == 0) {
6750         char *p, *q, *r = "";
6751         p = strchr(message, '{');
6752         if (p) {
6753             q = strchr(p, '}');
6754             if (q) {
6755                 *q = NULLCHAR;
6756                 r = p + 1;
6757             }
6758         }
6759             
6760         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6761         return;
6762
6763     } else if (strncmp(message, "White resign", 12) == 0) {
6764         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6765         return;
6766     } else if (strncmp(message, "Black resign", 12) == 0) {
6767         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6768         return;
6769     } else if (strncmp(message, "White matches", 13) == 0 ||
6770                strncmp(message, "Black matches", 13) == 0   ) {
6771         /* [HGM] ignore GNUShogi noises */
6772         return;
6773     } else if (strncmp(message, "White", 5) == 0 &&
6774                message[5] != '(' &&
6775                StrStr(message, "Black") == NULL) {
6776         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6777         return;
6778     } else if (strncmp(message, "Black", 5) == 0 &&
6779                message[5] != '(') {
6780         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6781         return;
6782     } else if (strcmp(message, "resign") == 0 ||
6783                strcmp(message, "computer resigns") == 0) {
6784         switch (gameMode) {
6785           case MachinePlaysBlack:
6786           case IcsPlayingBlack:
6787             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6788             break;
6789           case MachinePlaysWhite:
6790           case IcsPlayingWhite:
6791             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6792             break;
6793           case TwoMachinesPlay:
6794             if (cps->twoMachinesColor[0] == 'w')
6795               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6796             else
6797               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6798             break;
6799           default:
6800             /* can't happen */
6801             break;
6802         }
6803         return;
6804     } else if (strncmp(message, "opponent mates", 14) == 0) {
6805         switch (gameMode) {
6806           case MachinePlaysBlack:
6807           case IcsPlayingBlack:
6808             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6809             break;
6810           case MachinePlaysWhite:
6811           case IcsPlayingWhite:
6812             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6813             break;
6814           case TwoMachinesPlay:
6815             if (cps->twoMachinesColor[0] == 'w')
6816               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6817             else
6818               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6819             break;
6820           default:
6821             /* can't happen */
6822             break;
6823         }
6824         return;
6825     } else if (strncmp(message, "computer mates", 14) == 0) {
6826         switch (gameMode) {
6827           case MachinePlaysBlack:
6828           case IcsPlayingBlack:
6829             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6830             break;
6831           case MachinePlaysWhite:
6832           case IcsPlayingWhite:
6833             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6834             break;
6835           case TwoMachinesPlay:
6836             if (cps->twoMachinesColor[0] == 'w')
6837               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6838             else
6839               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6840             break;
6841           default:
6842             /* can't happen */
6843             break;
6844         }
6845         return;
6846     } else if (strncmp(message, "checkmate", 9) == 0) {
6847         if (WhiteOnMove(forwardMostMove)) {
6848             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6849         } else {
6850             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6851         }
6852         return;
6853     } else if (strstr(message, "Draw") != NULL ||
6854                strstr(message, "game is a draw") != NULL) {
6855         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6856         return;
6857     } else if (strstr(message, "offer") != NULL &&
6858                strstr(message, "draw") != NULL) {
6859 #if ZIPPY
6860         if (appData.zippyPlay && first.initDone) {
6861             /* Relay offer to ICS */
6862             SendToICS(ics_prefix);
6863             SendToICS("draw\n");
6864         }
6865 #endif
6866         cps->offeredDraw = 2; /* valid until this engine moves twice */
6867         if (gameMode == TwoMachinesPlay) {
6868             if (cps->other->offeredDraw) {
6869                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6870             /* [HGM] in two-machine mode we delay relaying draw offer      */
6871             /* until after we also have move, to see if it is really claim */
6872             }
6873         } else if (gameMode == MachinePlaysWhite ||
6874                    gameMode == MachinePlaysBlack) {
6875           if (userOfferedDraw) {
6876             DisplayInformation(_("Machine accepts your draw offer"));
6877             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6878           } else {
6879             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6880           }
6881         }
6882     }
6883
6884     
6885     /*
6886      * Look for thinking output
6887      */
6888     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6889           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6890                                 ) {
6891         int plylev, mvleft, mvtot, curscore, time;
6892         char mvname[MOVE_LEN];
6893         u64 nodes; // [DM]
6894         char plyext;
6895         int ignore = FALSE;
6896         int prefixHint = FALSE;
6897         mvname[0] = NULLCHAR;
6898
6899         switch (gameMode) {
6900           case MachinePlaysBlack:
6901           case IcsPlayingBlack:
6902             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6903             break;
6904           case MachinePlaysWhite:
6905           case IcsPlayingWhite:
6906             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6907             break;
6908           case AnalyzeMode:
6909           case AnalyzeFile:
6910             break;
6911           case IcsObserving: /* [DM] icsEngineAnalyze */
6912             if (!appData.icsEngineAnalyze) ignore = TRUE;
6913             break;
6914           case TwoMachinesPlay:
6915             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6916                 ignore = TRUE;
6917             }
6918             break;
6919           default:
6920             ignore = TRUE;
6921             break;
6922         }
6923
6924         if (!ignore) {
6925             buf1[0] = NULLCHAR;
6926             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6927                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6928
6929                 if (plyext != ' ' && plyext != '\t') {
6930                     time *= 100;
6931                 }
6932
6933                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6934                 if( cps->scoreIsAbsolute && 
6935                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6936                 {
6937                     curscore = -curscore;
6938                 }
6939
6940
6941                 programStats.depth = plylev;
6942                 programStats.nodes = nodes;
6943                 programStats.time = time;
6944                 programStats.score = curscore;
6945                 programStats.got_only_move = 0;
6946
6947                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6948                         int ticklen;
6949
6950                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6951                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6952                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6953                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6954                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6955                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6956                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6957                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6958                 }
6959
6960                 /* Buffer overflow protection */
6961                 if (buf1[0] != NULLCHAR) {
6962                     if (strlen(buf1) >= sizeof(programStats.movelist)
6963                         && appData.debugMode) {
6964                         fprintf(debugFP,
6965                                 "PV is too long; using the first %u bytes.\n",
6966                                 (unsigned) sizeof(programStats.movelist) - 1);
6967                     }
6968
6969                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6970                 } else {
6971                     sprintf(programStats.movelist, " no PV\n");
6972                 }
6973
6974                 if (programStats.seen_stat) {
6975                     programStats.ok_to_send = 1;
6976                 }
6977
6978                 if (strchr(programStats.movelist, '(') != NULL) {
6979                     programStats.line_is_book = 1;
6980                     programStats.nr_moves = 0;
6981                     programStats.moves_left = 0;
6982                 } else {
6983                     programStats.line_is_book = 0;
6984                 }
6985
6986                 SendProgramStatsToFrontend( cps, &programStats );
6987
6988                 /* 
6989                     [AS] Protect the thinkOutput buffer from overflow... this
6990                     is only useful if buf1 hasn't overflowed first!
6991                 */
6992                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6993                         plylev, 
6994                         (gameMode == TwoMachinesPlay ?
6995                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6996                         ((double) curscore) / 100.0,
6997                         prefixHint ? lastHint : "",
6998                         prefixHint ? " " : "" );
6999
7000                 if( buf1[0] != NULLCHAR ) {
7001                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7002
7003                     if( strlen(buf1) > max_len ) {
7004                         if( appData.debugMode) {
7005                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7006                         }
7007                         buf1[max_len+1] = '\0';
7008                     }
7009
7010                     strcat( thinkOutput, buf1 );
7011                 }
7012
7013                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7014                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7015                     DisplayMove(currentMove - 1);
7016                 }
7017                 return;
7018
7019             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7020                 /* crafty (9.25+) says "(only move) <move>"
7021                  * if there is only 1 legal move
7022                  */
7023                 sscanf(p, "(only move) %s", buf1);
7024                 sprintf(thinkOutput, "%s (only move)", buf1);
7025                 sprintf(programStats.movelist, "%s (only move)", buf1);
7026                 programStats.depth = 1;
7027                 programStats.nr_moves = 1;
7028                 programStats.moves_left = 1;
7029                 programStats.nodes = 1;
7030                 programStats.time = 1;
7031                 programStats.got_only_move = 1;
7032
7033                 /* Not really, but we also use this member to
7034                    mean "line isn't going to change" (Crafty
7035                    isn't searching, so stats won't change) */
7036                 programStats.line_is_book = 1;
7037
7038                 SendProgramStatsToFrontend( cps, &programStats );
7039                 
7040                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7041                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7042                     DisplayMove(currentMove - 1);
7043                 }
7044                 return;
7045             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7046                               &time, &nodes, &plylev, &mvleft,
7047                               &mvtot, mvname) >= 5) {
7048                 /* The stat01: line is from Crafty (9.29+) in response
7049                    to the "." command */
7050                 programStats.seen_stat = 1;
7051                 cps->maybeThinking = TRUE;
7052
7053                 if (programStats.got_only_move || !appData.periodicUpdates)
7054                   return;
7055
7056                 programStats.depth = plylev;
7057                 programStats.time = time;
7058                 programStats.nodes = nodes;
7059                 programStats.moves_left = mvleft;
7060                 programStats.nr_moves = mvtot;
7061                 strcpy(programStats.move_name, mvname);
7062                 programStats.ok_to_send = 1;
7063                 programStats.movelist[0] = '\0';
7064
7065                 SendProgramStatsToFrontend( cps, &programStats );
7066
7067                 return;
7068
7069             } else if (strncmp(message,"++",2) == 0) {
7070                 /* Crafty 9.29+ outputs this */
7071                 programStats.got_fail = 2;
7072                 return;
7073
7074             } else if (strncmp(message,"--",2) == 0) {
7075                 /* Crafty 9.29+ outputs this */
7076                 programStats.got_fail = 1;
7077                 return;
7078
7079             } else if (thinkOutput[0] != NULLCHAR &&
7080                        strncmp(message, "    ", 4) == 0) {
7081                 unsigned message_len;
7082
7083                 p = message;
7084                 while (*p && *p == ' ') p++;
7085
7086                 message_len = strlen( p );
7087
7088                 /* [AS] Avoid buffer overflow */
7089                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7090                     strcat(thinkOutput, " ");
7091                     strcat(thinkOutput, p);
7092                 }
7093
7094                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7095                     strcat(programStats.movelist, " ");
7096                     strcat(programStats.movelist, p);
7097                 }
7098
7099                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7100                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7101                     DisplayMove(currentMove - 1);
7102                 }
7103                 return;
7104             }
7105         }
7106         else {
7107             buf1[0] = NULLCHAR;
7108
7109             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7110                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7111             {
7112                 ChessProgramStats cpstats;
7113
7114                 if (plyext != ' ' && plyext != '\t') {
7115                     time *= 100;
7116                 }
7117
7118                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7119                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7120                     curscore = -curscore;
7121                 }
7122
7123                 cpstats.depth = plylev;
7124                 cpstats.nodes = nodes;
7125                 cpstats.time = time;
7126                 cpstats.score = curscore;
7127                 cpstats.got_only_move = 0;
7128                 cpstats.movelist[0] = '\0';
7129
7130                 if (buf1[0] != NULLCHAR) {
7131                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7132                 }
7133
7134                 cpstats.ok_to_send = 0;
7135                 cpstats.line_is_book = 0;
7136                 cpstats.nr_moves = 0;
7137                 cpstats.moves_left = 0;
7138
7139                 SendProgramStatsToFrontend( cps, &cpstats );
7140             }
7141         }
7142     }
7143 }
7144
7145
7146 /* Parse a game score from the character string "game", and
7147    record it as the history of the current game.  The game
7148    score is NOT assumed to start from the standard position. 
7149    The display is not updated in any way.
7150    */
7151 void
7152 ParseGameHistory(game)
7153      char *game;
7154 {
7155     ChessMove moveType;
7156     int fromX, fromY, toX, toY, boardIndex;
7157     char promoChar;
7158     char *p, *q;
7159     char buf[MSG_SIZ];
7160
7161     if (appData.debugMode)
7162       fprintf(debugFP, "Parsing game history: %s\n", game);
7163
7164     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7165     gameInfo.site = StrSave(appData.icsHost);
7166     gameInfo.date = PGNDate();
7167     gameInfo.round = StrSave("-");
7168
7169     /* Parse out names of players */
7170     while (*game == ' ') game++;
7171     p = buf;
7172     while (*game != ' ') *p++ = *game++;
7173     *p = NULLCHAR;
7174     gameInfo.white = StrSave(buf);
7175     while (*game == ' ') game++;
7176     p = buf;
7177     while (*game != ' ' && *game != '\n') *p++ = *game++;
7178     *p = NULLCHAR;
7179     gameInfo.black = StrSave(buf);
7180
7181     /* Parse moves */
7182     boardIndex = blackPlaysFirst ? 1 : 0;
7183     yynewstr(game);
7184     for (;;) {
7185         yyboardindex = boardIndex;
7186         moveType = (ChessMove) yylex();
7187         switch (moveType) {
7188           case IllegalMove:             /* maybe suicide chess, etc. */
7189   if (appData.debugMode) {
7190     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7191     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7192     setbuf(debugFP, NULL);
7193   }
7194           case WhitePromotionChancellor:
7195           case BlackPromotionChancellor:
7196           case WhitePromotionArchbishop:
7197           case BlackPromotionArchbishop:
7198           case WhitePromotionQueen:
7199           case BlackPromotionQueen:
7200           case WhitePromotionRook:
7201           case BlackPromotionRook:
7202           case WhitePromotionBishop:
7203           case BlackPromotionBishop:
7204           case WhitePromotionKnight:
7205           case BlackPromotionKnight:
7206           case WhitePromotionKing:
7207           case BlackPromotionKing:
7208           case NormalMove:
7209           case WhiteCapturesEnPassant:
7210           case BlackCapturesEnPassant:
7211           case WhiteKingSideCastle:
7212           case WhiteQueenSideCastle:
7213           case BlackKingSideCastle:
7214           case BlackQueenSideCastle:
7215           case WhiteKingSideCastleWild:
7216           case WhiteQueenSideCastleWild:
7217           case BlackKingSideCastleWild:
7218           case BlackQueenSideCastleWild:
7219           /* PUSH Fabien */
7220           case WhiteHSideCastleFR:
7221           case WhiteASideCastleFR:
7222           case BlackHSideCastleFR:
7223           case BlackASideCastleFR:
7224           /* POP Fabien */
7225             fromX = currentMoveString[0] - AAA;
7226             fromY = currentMoveString[1] - ONE;
7227             toX = currentMoveString[2] - AAA;
7228             toY = currentMoveString[3] - ONE;
7229             promoChar = currentMoveString[4];
7230             break;
7231           case WhiteDrop:
7232           case BlackDrop:
7233             fromX = moveType == WhiteDrop ?
7234               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7235             (int) CharToPiece(ToLower(currentMoveString[0]));
7236             fromY = DROP_RANK;
7237             toX = currentMoveString[2] - AAA;
7238             toY = currentMoveString[3] - ONE;
7239             promoChar = NULLCHAR;
7240             break;
7241           case AmbiguousMove:
7242             /* bug? */
7243             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7244   if (appData.debugMode) {
7245     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247     setbuf(debugFP, NULL);
7248   }
7249             DisplayError(buf, 0);
7250             return;
7251           case ImpossibleMove:
7252             /* bug? */
7253             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7254   if (appData.debugMode) {
7255     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7256     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7257     setbuf(debugFP, NULL);
7258   }
7259             DisplayError(buf, 0);
7260             return;
7261           case (ChessMove) 0:   /* end of file */
7262             if (boardIndex < backwardMostMove) {
7263                 /* Oops, gap.  How did that happen? */
7264                 DisplayError(_("Gap in move list"), 0);
7265                 return;
7266             }
7267             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7268             if (boardIndex > forwardMostMove) {
7269                 forwardMostMove = boardIndex;
7270             }
7271             return;
7272           case ElapsedTime:
7273             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7274                 strcat(parseList[boardIndex-1], " ");
7275                 strcat(parseList[boardIndex-1], yy_text);
7276             }
7277             continue;
7278           case Comment:
7279           case PGNTag:
7280           case NAG:
7281           default:
7282             /* ignore */
7283             continue;
7284           case WhiteWins:
7285           case BlackWins:
7286           case GameIsDrawn:
7287           case GameUnfinished:
7288             if (gameMode == IcsExamining) {
7289                 if (boardIndex < backwardMostMove) {
7290                     /* Oops, gap.  How did that happen? */
7291                     return;
7292                 }
7293                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7294                 return;
7295             }
7296             gameInfo.result = moveType;
7297             p = strchr(yy_text, '{');
7298             if (p == NULL) p = strchr(yy_text, '(');
7299             if (p == NULL) {
7300                 p = yy_text;
7301                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7302             } else {
7303                 q = strchr(p, *p == '{' ? '}' : ')');
7304                 if (q != NULL) *q = NULLCHAR;
7305                 p++;
7306             }
7307             gameInfo.resultDetails = StrSave(p);
7308             continue;
7309         }
7310         if (boardIndex >= forwardMostMove &&
7311             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7312             backwardMostMove = blackPlaysFirst ? 1 : 0;
7313             return;
7314         }
7315         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7316                                  fromY, fromX, toY, toX, promoChar,
7317                                  parseList[boardIndex]);
7318         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7319         /* currentMoveString is set as a side-effect of yylex */
7320         strcpy(moveList[boardIndex], currentMoveString);
7321         strcat(moveList[boardIndex], "\n");
7322         boardIndex++;
7323         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7324         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7325           case MT_NONE:
7326           case MT_STALEMATE:
7327           default:
7328             break;
7329           case MT_CHECK:
7330             if(gameInfo.variant != VariantShogi)
7331                 strcat(parseList[boardIndex - 1], "+");
7332             break;
7333           case MT_CHECKMATE:
7334           case MT_STAINMATE:
7335             strcat(parseList[boardIndex - 1], "#");
7336             break;
7337         }
7338     }
7339 }
7340
7341
7342 /* Apply a move to the given board  */
7343 void
7344 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7345      int fromX, fromY, toX, toY;
7346      int promoChar;
7347      Board board;
7348 {
7349   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7350
7351     /* [HGM] compute & store e.p. status and castling rights for new position */
7352     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7353     { int i;
7354
7355       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7356       oldEP = (signed char)board[EP_STATUS];
7357       board[EP_STATUS] = EP_NONE;
7358
7359       if( board[toY][toX] != EmptySquare ) 
7360            board[EP_STATUS] = EP_CAPTURE;  
7361
7362       if( board[fromY][fromX] == WhitePawn ) {
7363            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7364                board[EP_STATUS] = EP_PAWN_MOVE;
7365            if( toY-fromY==2) {
7366                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7367                         gameInfo.variant != VariantBerolina || toX < fromX)
7368                       board[EP_STATUS] = toX | berolina;
7369                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7370                         gameInfo.variant != VariantBerolina || toX > fromX) 
7371                       board[EP_STATUS] = toX;
7372            }
7373       } else 
7374       if( board[fromY][fromX] == BlackPawn ) {
7375            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7376                board[EP_STATUS] = EP_PAWN_MOVE; 
7377            if( toY-fromY== -2) {
7378                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7379                         gameInfo.variant != VariantBerolina || toX < fromX)
7380                       board[EP_STATUS] = toX | berolina;
7381                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7382                         gameInfo.variant != VariantBerolina || toX > fromX) 
7383                       board[EP_STATUS] = toX;
7384            }
7385        }
7386
7387        for(i=0; i<nrCastlingRights; i++) {
7388            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7389               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7390              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7391        }
7392
7393     }
7394
7395   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7396   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7397        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7398          
7399   if (fromX == toX && fromY == toY) return;
7400
7401   if (fromY == DROP_RANK) {
7402         /* must be first */
7403         piece = board[toY][toX] = (ChessSquare) fromX;
7404   } else {
7405      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7406      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7407      if(gameInfo.variant == VariantKnightmate)
7408          king += (int) WhiteUnicorn - (int) WhiteKing;
7409
7410     /* Code added by Tord: */
7411     /* FRC castling assumed when king captures friendly rook. */
7412     if (board[fromY][fromX] == WhiteKing &&
7413              board[toY][toX] == WhiteRook) {
7414       board[fromY][fromX] = EmptySquare;
7415       board[toY][toX] = EmptySquare;
7416       if(toX > fromX) {
7417         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7418       } else {
7419         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7420       }
7421     } else if (board[fromY][fromX] == BlackKing &&
7422                board[toY][toX] == BlackRook) {
7423       board[fromY][fromX] = EmptySquare;
7424       board[toY][toX] = EmptySquare;
7425       if(toX > fromX) {
7426         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7427       } else {
7428         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7429       }
7430     /* End of code added by Tord */
7431
7432     } else if (board[fromY][fromX] == king
7433         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7434         && toY == fromY && toX > fromX+1) {
7435         board[fromY][fromX] = EmptySquare;
7436         board[toY][toX] = king;
7437         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7438         board[fromY][BOARD_RGHT-1] = EmptySquare;
7439     } else if (board[fromY][fromX] == king
7440         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7441                && toY == fromY && toX < fromX-1) {
7442         board[fromY][fromX] = EmptySquare;
7443         board[toY][toX] = king;
7444         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7445         board[fromY][BOARD_LEFT] = EmptySquare;
7446     } else if (board[fromY][fromX] == WhitePawn
7447                && toY == BOARD_HEIGHT-1
7448                && gameInfo.variant != VariantXiangqi
7449                ) {
7450         /* white pawn promotion */
7451         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7452         if (board[toY][toX] == EmptySquare) {
7453             board[toY][toX] = WhiteQueen;
7454         }
7455         if(gameInfo.variant==VariantBughouse ||
7456            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7457             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7458         board[fromY][fromX] = EmptySquare;
7459     } else if ((fromY == BOARD_HEIGHT-4)
7460                && (toX != fromX)
7461                && gameInfo.variant != VariantXiangqi
7462                && gameInfo.variant != VariantBerolina
7463                && (board[fromY][fromX] == WhitePawn)
7464                && (board[toY][toX] == EmptySquare)) {
7465         board[fromY][fromX] = EmptySquare;
7466         board[toY][toX] = WhitePawn;
7467         captured = board[toY - 1][toX];
7468         board[toY - 1][toX] = EmptySquare;
7469     } else if ((fromY == BOARD_HEIGHT-4)
7470                && (toX == fromX)
7471                && gameInfo.variant == VariantBerolina
7472                && (board[fromY][fromX] == WhitePawn)
7473                && (board[toY][toX] == EmptySquare)) {
7474         board[fromY][fromX] = EmptySquare;
7475         board[toY][toX] = WhitePawn;
7476         if(oldEP & EP_BEROLIN_A) {
7477                 captured = board[fromY][fromX-1];
7478                 board[fromY][fromX-1] = EmptySquare;
7479         }else{  captured = board[fromY][fromX+1];
7480                 board[fromY][fromX+1] = EmptySquare;
7481         }
7482     } else if (board[fromY][fromX] == king
7483         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7484                && toY == fromY && toX > fromX+1) {
7485         board[fromY][fromX] = EmptySquare;
7486         board[toY][toX] = king;
7487         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7488         board[fromY][BOARD_RGHT-1] = EmptySquare;
7489     } else if (board[fromY][fromX] == king
7490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7491                && toY == fromY && toX < fromX-1) {
7492         board[fromY][fromX] = EmptySquare;
7493         board[toY][toX] = king;
7494         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7495         board[fromY][BOARD_LEFT] = EmptySquare;
7496     } else if (fromY == 7 && fromX == 3
7497                && board[fromY][fromX] == BlackKing
7498                && toY == 7 && toX == 5) {
7499         board[fromY][fromX] = EmptySquare;
7500         board[toY][toX] = BlackKing;
7501         board[fromY][7] = EmptySquare;
7502         board[toY][4] = BlackRook;
7503     } else if (fromY == 7 && fromX == 3
7504                && board[fromY][fromX] == BlackKing
7505                && toY == 7 && toX == 1) {
7506         board[fromY][fromX] = EmptySquare;
7507         board[toY][toX] = BlackKing;
7508         board[fromY][0] = EmptySquare;
7509         board[toY][2] = BlackRook;
7510     } else if (board[fromY][fromX] == BlackPawn
7511                && toY == 0
7512                && gameInfo.variant != VariantXiangqi
7513                ) {
7514         /* black pawn promotion */
7515         board[0][toX] = CharToPiece(ToLower(promoChar));
7516         if (board[0][toX] == EmptySquare) {
7517             board[0][toX] = BlackQueen;
7518         }
7519         if(gameInfo.variant==VariantBughouse ||
7520            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7521             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7522         board[fromY][fromX] = EmptySquare;
7523     } else if ((fromY == 3)
7524                && (toX != fromX)
7525                && gameInfo.variant != VariantXiangqi
7526                && gameInfo.variant != VariantBerolina
7527                && (board[fromY][fromX] == BlackPawn)
7528                && (board[toY][toX] == EmptySquare)) {
7529         board[fromY][fromX] = EmptySquare;
7530         board[toY][toX] = BlackPawn;
7531         captured = board[toY + 1][toX];
7532         board[toY + 1][toX] = EmptySquare;
7533     } else if ((fromY == 3)
7534                && (toX == fromX)
7535                && gameInfo.variant == VariantBerolina
7536                && (board[fromY][fromX] == BlackPawn)
7537                && (board[toY][toX] == EmptySquare)) {
7538         board[fromY][fromX] = EmptySquare;
7539         board[toY][toX] = BlackPawn;
7540         if(oldEP & EP_BEROLIN_A) {
7541                 captured = board[fromY][fromX-1];
7542                 board[fromY][fromX-1] = EmptySquare;
7543         }else{  captured = board[fromY][fromX+1];
7544                 board[fromY][fromX+1] = EmptySquare;
7545         }
7546     } else {
7547         board[toY][toX] = board[fromY][fromX];
7548         board[fromY][fromX] = EmptySquare;
7549     }
7550
7551     /* [HGM] now we promote for Shogi, if needed */
7552     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7553         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7554   }
7555
7556     if (gameInfo.holdingsWidth != 0) {
7557
7558       /* !!A lot more code needs to be written to support holdings  */
7559       /* [HGM] OK, so I have written it. Holdings are stored in the */
7560       /* penultimate board files, so they are automaticlly stored   */
7561       /* in the game history.                                       */
7562       if (fromY == DROP_RANK) {
7563         /* Delete from holdings, by decreasing count */
7564         /* and erasing image if necessary            */
7565         p = (int) fromX;
7566         if(p < (int) BlackPawn) { /* white drop */
7567              p -= (int)WhitePawn;
7568                  p = PieceToNumber((ChessSquare)p);
7569              if(p >= gameInfo.holdingsSize) p = 0;
7570              if(--board[p][BOARD_WIDTH-2] <= 0)
7571                   board[p][BOARD_WIDTH-1] = EmptySquare;
7572              if((int)board[p][BOARD_WIDTH-2] < 0)
7573                         board[p][BOARD_WIDTH-2] = 0;
7574         } else {                  /* black drop */
7575              p -= (int)BlackPawn;
7576                  p = PieceToNumber((ChessSquare)p);
7577              if(p >= gameInfo.holdingsSize) p = 0;
7578              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7579                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7580              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7581                         board[BOARD_HEIGHT-1-p][1] = 0;
7582         }
7583       }
7584       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7585           && gameInfo.variant != VariantBughouse        ) {
7586         /* [HGM] holdings: Add to holdings, if holdings exist */
7587         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7588                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7589                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7590         }
7591         p = (int) captured;
7592         if (p >= (int) BlackPawn) {
7593           p -= (int)BlackPawn;
7594           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7595                   /* in Shogi restore piece to its original  first */
7596                   captured = (ChessSquare) (DEMOTED captured);
7597                   p = DEMOTED p;
7598           }
7599           p = PieceToNumber((ChessSquare)p);
7600           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7601           board[p][BOARD_WIDTH-2]++;
7602           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7603         } else {
7604           p -= (int)WhitePawn;
7605           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7606                   captured = (ChessSquare) (DEMOTED captured);
7607                   p = DEMOTED p;
7608           }
7609           p = PieceToNumber((ChessSquare)p);
7610           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7611           board[BOARD_HEIGHT-1-p][1]++;
7612           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7613         }
7614       }
7615     } else if (gameInfo.variant == VariantAtomic) {
7616       if (captured != EmptySquare) {
7617         int y, x;
7618         for (y = toY-1; y <= toY+1; y++) {
7619           for (x = toX-1; x <= toX+1; x++) {
7620             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7621                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7622               board[y][x] = EmptySquare;
7623             }
7624           }
7625         }
7626         board[toY][toX] = EmptySquare;
7627       }
7628     }
7629     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7630         /* [HGM] Shogi promotions */
7631         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7632     }
7633
7634     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7635                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7636         // [HGM] superchess: take promotion piece out of holdings
7637         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7638         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7639             if(!--board[k][BOARD_WIDTH-2])
7640                 board[k][BOARD_WIDTH-1] = EmptySquare;
7641         } else {
7642             if(!--board[BOARD_HEIGHT-1-k][1])
7643                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7644         }
7645     }
7646
7647 }
7648
7649 /* Updates forwardMostMove */
7650 void
7651 MakeMove(fromX, fromY, toX, toY, promoChar)
7652      int fromX, fromY, toX, toY;
7653      int promoChar;
7654 {
7655 //    forwardMostMove++; // [HGM] bare: moved downstream
7656
7657     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7658         int timeLeft; static int lastLoadFlag=0; int king, piece;
7659         piece = boards[forwardMostMove][fromY][fromX];
7660         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7661         if(gameInfo.variant == VariantKnightmate)
7662             king += (int) WhiteUnicorn - (int) WhiteKing;
7663         if(forwardMostMove == 0) {
7664             if(blackPlaysFirst) 
7665                 fprintf(serverMoves, "%s;", second.tidy);
7666             fprintf(serverMoves, "%s;", first.tidy);
7667             if(!blackPlaysFirst) 
7668                 fprintf(serverMoves, "%s;", second.tidy);
7669         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7670         lastLoadFlag = loadFlag;
7671         // print base move
7672         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7673         // print castling suffix
7674         if( toY == fromY && piece == king ) {
7675             if(toX-fromX > 1)
7676                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7677             if(fromX-toX >1)
7678                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7679         }
7680         // e.p. suffix
7681         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7682              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7683              boards[forwardMostMove][toY][toX] == EmptySquare
7684              && fromX != toX )
7685                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7686         // promotion suffix
7687         if(promoChar != NULLCHAR)
7688                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7689         if(!loadFlag) {
7690             fprintf(serverMoves, "/%d/%d",
7691                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7692             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7693             else                      timeLeft = blackTimeRemaining/1000;
7694             fprintf(serverMoves, "/%d", timeLeft);
7695         }
7696         fflush(serverMoves);
7697     }
7698
7699     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7700       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7701                         0, 1);
7702       return;
7703     }
7704     if (commentList[forwardMostMove+1] != NULL) {
7705         free(commentList[forwardMostMove+1]);
7706         commentList[forwardMostMove+1] = NULL;
7707     }
7708     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7709     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7710     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7711     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7712     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7713     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7714     gameInfo.result = GameUnfinished;
7715     if (gameInfo.resultDetails != NULL) {
7716         free(gameInfo.resultDetails);
7717         gameInfo.resultDetails = NULL;
7718     }
7719     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7720                               moveList[forwardMostMove - 1]);
7721     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7722                              PosFlags(forwardMostMove - 1),
7723                              fromY, fromX, toY, toX, promoChar,
7724                              parseList[forwardMostMove - 1]);
7725     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7726       case MT_NONE:
7727       case MT_STALEMATE:
7728       default:
7729         break;
7730       case MT_CHECK:
7731         if(gameInfo.variant != VariantShogi)
7732             strcat(parseList[forwardMostMove - 1], "+");
7733         break;
7734       case MT_CHECKMATE:
7735       case MT_STAINMATE:
7736         strcat(parseList[forwardMostMove - 1], "#");
7737         break;
7738     }
7739     if (appData.debugMode) {
7740         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7741     }
7742
7743 }
7744
7745 /* Updates currentMove if not pausing */
7746 void
7747 ShowMove(fromX, fromY, toX, toY)
7748 {
7749     int instant = (gameMode == PlayFromGameFile) ?
7750         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7751     if(appData.noGUI) return;
7752     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7753         if (!instant) {
7754             if (forwardMostMove == currentMove + 1) {
7755                 AnimateMove(boards[forwardMostMove - 1],
7756                             fromX, fromY, toX, toY);
7757             }
7758             if (appData.highlightLastMove) {
7759                 SetHighlights(fromX, fromY, toX, toY);
7760             }
7761         }
7762         currentMove = forwardMostMove;
7763     }
7764
7765     if (instant) return;
7766
7767     DisplayMove(currentMove - 1);
7768     DrawPosition(FALSE, boards[currentMove]);
7769     DisplayBothClocks();
7770     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7771 }
7772
7773 void SendEgtPath(ChessProgramState *cps)
7774 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7775         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7776
7777         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7778
7779         while(*p) {
7780             char c, *q = name+1, *r, *s;
7781
7782             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7783             while(*p && *p != ',') *q++ = *p++;
7784             *q++ = ':'; *q = 0;
7785             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7786                 strcmp(name, ",nalimov:") == 0 ) {
7787                 // take nalimov path from the menu-changeable option first, if it is defined
7788                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7789                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7790             } else
7791             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7792                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7793                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7794                 s = r = StrStr(s, ":") + 1; // beginning of path info
7795                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7796                 c = *r; *r = 0;             // temporarily null-terminate path info
7797                     *--q = 0;               // strip of trailig ':' from name
7798                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7799                 *r = c;
7800                 SendToProgram(buf,cps);     // send egtbpath command for this format
7801             }
7802             if(*p == ',') p++; // read away comma to position for next format name
7803         }
7804 }
7805
7806 void
7807 InitChessProgram(cps, setup)
7808      ChessProgramState *cps;
7809      int setup; /* [HGM] needed to setup FRC opening position */
7810 {
7811     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7812     if (appData.noChessProgram) return;
7813     hintRequested = FALSE;
7814     bookRequested = FALSE;
7815
7816     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7817     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7818     if(cps->memSize) { /* [HGM] memory */
7819         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7820         SendToProgram(buf, cps);
7821     }
7822     SendEgtPath(cps); /* [HGM] EGT */
7823     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7824         sprintf(buf, "cores %d\n", appData.smpCores);
7825         SendToProgram(buf, cps);
7826     }
7827
7828     SendToProgram(cps->initString, cps);
7829     if (gameInfo.variant != VariantNormal &&
7830         gameInfo.variant != VariantLoadable
7831         /* [HGM] also send variant if board size non-standard */
7832         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7833                                             ) {
7834       char *v = VariantName(gameInfo.variant);
7835       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7836         /* [HGM] in protocol 1 we have to assume all variants valid */
7837         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7838         DisplayFatalError(buf, 0, 1);
7839         return;
7840       }
7841
7842       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7843       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7844       if( gameInfo.variant == VariantXiangqi )
7845            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7846       if( gameInfo.variant == VariantShogi )
7847            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7848       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7849            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7850       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7851                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7852            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7853       if( gameInfo.variant == VariantCourier )
7854            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7855       if( gameInfo.variant == VariantSuper )
7856            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7857       if( gameInfo.variant == VariantGreat )
7858            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7859
7860       if(overruled) {
7861            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7862                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7863            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7864            if(StrStr(cps->variants, b) == NULL) { 
7865                // specific sized variant not known, check if general sizing allowed
7866                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7867                    if(StrStr(cps->variants, "boardsize") == NULL) {
7868                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7869                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7870                        DisplayFatalError(buf, 0, 1);
7871                        return;
7872                    }
7873                    /* [HGM] here we really should compare with the maximum supported board size */
7874                }
7875            }
7876       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7877       sprintf(buf, "variant %s\n", b);
7878       SendToProgram(buf, cps);
7879     }
7880     currentlyInitializedVariant = gameInfo.variant;
7881
7882     /* [HGM] send opening position in FRC to first engine */
7883     if(setup) {
7884           SendToProgram("force\n", cps);
7885           SendBoard(cps, 0);
7886           /* engine is now in force mode! Set flag to wake it up after first move. */
7887           setboardSpoiledMachineBlack = 1;
7888     }
7889
7890     if (cps->sendICS) {
7891       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7892       SendToProgram(buf, cps);
7893     }
7894     cps->maybeThinking = FALSE;
7895     cps->offeredDraw = 0;
7896     if (!appData.icsActive) {
7897         SendTimeControl(cps, movesPerSession, timeControl,
7898                         timeIncrement, appData.searchDepth,
7899                         searchTime);
7900     }
7901     if (appData.showThinking 
7902         // [HGM] thinking: four options require thinking output to be sent
7903         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7904                                 ) {
7905         SendToProgram("post\n", cps);
7906     }
7907     SendToProgram("hard\n", cps);
7908     if (!appData.ponderNextMove) {
7909         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7910            it without being sure what state we are in first.  "hard"
7911            is not a toggle, so that one is OK.
7912          */
7913         SendToProgram("easy\n", cps);
7914     }
7915     if (cps->usePing) {
7916       sprintf(buf, "ping %d\n", ++cps->lastPing);
7917       SendToProgram(buf, cps);
7918     }
7919     cps->initDone = TRUE;
7920 }   
7921
7922
7923 void
7924 StartChessProgram(cps)
7925      ChessProgramState *cps;
7926 {
7927     char buf[MSG_SIZ];
7928     int err;
7929
7930     if (appData.noChessProgram) return;
7931     cps->initDone = FALSE;
7932
7933     if (strcmp(cps->host, "localhost") == 0) {
7934         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7935     } else if (*appData.remoteShell == NULLCHAR) {
7936         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7937     } else {
7938         if (*appData.remoteUser == NULLCHAR) {
7939           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7940                     cps->program);
7941         } else {
7942           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7943                     cps->host, appData.remoteUser, cps->program);
7944         }
7945         err = StartChildProcess(buf, "", &cps->pr);
7946     }
7947     
7948     if (err != 0) {
7949         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7950         DisplayFatalError(buf, err, 1);
7951         cps->pr = NoProc;
7952         cps->isr = NULL;
7953         return;
7954     }
7955     
7956     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7957     if (cps->protocolVersion > 1) {
7958       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7959       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7960       cps->comboCnt = 0;  //                and values of combo boxes
7961       SendToProgram(buf, cps);
7962     } else {
7963       SendToProgram("xboard\n", cps);
7964     }
7965 }
7966
7967
7968 void
7969 TwoMachinesEventIfReady P((void))
7970 {
7971   if (first.lastPing != first.lastPong) {
7972     DisplayMessage("", _("Waiting for first chess program"));
7973     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7974     return;
7975   }
7976   if (second.lastPing != second.lastPong) {
7977     DisplayMessage("", _("Waiting for second chess program"));
7978     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7979     return;
7980   }
7981   ThawUI();
7982   TwoMachinesEvent();
7983 }
7984
7985 void
7986 NextMatchGame P((void))
7987 {
7988     int index; /* [HGM] autoinc: step load index during match */
7989     Reset(FALSE, TRUE);
7990     if (*appData.loadGameFile != NULLCHAR) {
7991         index = appData.loadGameIndex;
7992         if(index < 0) { // [HGM] autoinc
7993             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7994             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7995         } 
7996         LoadGameFromFile(appData.loadGameFile,
7997                          index,
7998                          appData.loadGameFile, FALSE);
7999     } else if (*appData.loadPositionFile != NULLCHAR) {
8000         index = appData.loadPositionIndex;
8001         if(index < 0) { // [HGM] autoinc
8002             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8003             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8004         } 
8005         LoadPositionFromFile(appData.loadPositionFile,
8006                              index,
8007                              appData.loadPositionFile);
8008     }
8009     TwoMachinesEventIfReady();
8010 }
8011
8012 void UserAdjudicationEvent( int result )
8013 {
8014     ChessMove gameResult = GameIsDrawn;
8015
8016     if( result > 0 ) {
8017         gameResult = WhiteWins;
8018     }
8019     else if( result < 0 ) {
8020         gameResult = BlackWins;
8021     }
8022
8023     if( gameMode == TwoMachinesPlay ) {
8024         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8025     }
8026 }
8027
8028
8029 // [HGM] save: calculate checksum of game to make games easily identifiable
8030 int StringCheckSum(char *s)
8031 {
8032         int i = 0;
8033         if(s==NULL) return 0;
8034         while(*s) i = i*259 + *s++;
8035         return i;
8036 }
8037
8038 int GameCheckSum()
8039 {
8040         int i, sum=0;
8041         for(i=backwardMostMove; i<forwardMostMove; i++) {
8042                 sum += pvInfoList[i].depth;
8043                 sum += StringCheckSum(parseList[i]);
8044                 sum += StringCheckSum(commentList[i]);
8045                 sum *= 261;
8046         }
8047         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8048         return sum + StringCheckSum(commentList[i]);
8049 } // end of save patch
8050
8051 void
8052 GameEnds(result, resultDetails, whosays)
8053      ChessMove result;
8054      char *resultDetails;
8055      int whosays;
8056 {
8057     GameMode nextGameMode;
8058     int isIcsGame;
8059     char buf[MSG_SIZ];
8060
8061     if(endingGame) return; /* [HGM] crash: forbid recursion */
8062     endingGame = 1;
8063
8064     if (appData.debugMode) {
8065       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8066               result, resultDetails ? resultDetails : "(null)", whosays);
8067     }
8068
8069     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8070         /* If we are playing on ICS, the server decides when the
8071            game is over, but the engine can offer to draw, claim 
8072            a draw, or resign. 
8073          */
8074 #if ZIPPY
8075         if (appData.zippyPlay && first.initDone) {
8076             if (result == GameIsDrawn) {
8077                 /* In case draw still needs to be claimed */
8078                 SendToICS(ics_prefix);
8079                 SendToICS("draw\n");
8080             } else if (StrCaseStr(resultDetails, "resign")) {
8081                 SendToICS(ics_prefix);
8082                 SendToICS("resign\n");
8083             }
8084         }
8085 #endif
8086         endingGame = 0; /* [HGM] crash */
8087         return;
8088     }
8089
8090     /* If we're loading the game from a file, stop */
8091     if (whosays == GE_FILE) {
8092       (void) StopLoadGameTimer();
8093       gameFileFP = NULL;
8094     }
8095
8096     /* Cancel draw offers */
8097     first.offeredDraw = second.offeredDraw = 0;
8098
8099     /* If this is an ICS game, only ICS can really say it's done;
8100        if not, anyone can. */
8101     isIcsGame = (gameMode == IcsPlayingWhite || 
8102                  gameMode == IcsPlayingBlack || 
8103                  gameMode == IcsObserving    || 
8104                  gameMode == IcsExamining);
8105
8106     if (!isIcsGame || whosays == GE_ICS) {
8107         /* OK -- not an ICS game, or ICS said it was done */
8108         StopClocks();
8109         if (!isIcsGame && !appData.noChessProgram) 
8110           SetUserThinkingEnables();
8111     
8112         /* [HGM] if a machine claims the game end we verify this claim */
8113         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8114             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8115                 char claimer;
8116                 ChessMove trueResult = (ChessMove) -1;
8117
8118                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8119                                             first.twoMachinesColor[0] :
8120                                             second.twoMachinesColor[0] ;
8121
8122                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8123                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8124                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8125                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8126                 } else
8127                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8128                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8129                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8130                 } else
8131                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8132                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8133                 }
8134
8135                 // now verify win claims, but not in drop games, as we don't understand those yet
8136                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8137                                                  || gameInfo.variant == VariantGreat) &&
8138                     (result == WhiteWins && claimer == 'w' ||
8139                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8140                       if (appData.debugMode) {
8141                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8142                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8143                       }
8144                       if(result != trueResult) {
8145                               sprintf(buf, "False win claim: '%s'", resultDetails);
8146                               result = claimer == 'w' ? BlackWins : WhiteWins;
8147                               resultDetails = buf;
8148                       }
8149                 } else
8150                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8151                     && (forwardMostMove <= backwardMostMove ||
8152                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8153                         (claimer=='b')==(forwardMostMove&1))
8154                                                                                   ) {
8155                       /* [HGM] verify: draws that were not flagged are false claims */
8156                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8157                       result = claimer == 'w' ? BlackWins : WhiteWins;
8158                       resultDetails = buf;
8159                 }
8160                 /* (Claiming a loss is accepted no questions asked!) */
8161             }
8162             /* [HGM] bare: don't allow bare King to win */
8163             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8164                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8165                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8166                && result != GameIsDrawn)
8167             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8168                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8169                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8170                         if(p >= 0 && p <= (int)WhiteKing) k++;
8171                 }
8172                 if (appData.debugMode) {
8173                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8174                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8175                 }
8176                 if(k <= 1) {
8177                         result = GameIsDrawn;
8178                         sprintf(buf, "%s but bare king", resultDetails);
8179                         resultDetails = buf;
8180                 }
8181             }
8182         }
8183
8184
8185         if(serverMoves != NULL && !loadFlag) { char c = '=';
8186             if(result==WhiteWins) c = '+';
8187             if(result==BlackWins) c = '-';
8188             if(resultDetails != NULL)
8189                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8190         }
8191         if (resultDetails != NULL) {
8192             gameInfo.result = result;
8193             gameInfo.resultDetails = StrSave(resultDetails);
8194
8195             /* display last move only if game was not loaded from file */
8196             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8197                 DisplayMove(currentMove - 1);
8198     
8199             if (forwardMostMove != 0) {
8200                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8201                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8202                                                                 ) {
8203                     if (*appData.saveGameFile != NULLCHAR) {
8204                         SaveGameToFile(appData.saveGameFile, TRUE);
8205                     } else if (appData.autoSaveGames) {
8206                         AutoSaveGame();
8207                     }
8208                     if (*appData.savePositionFile != NULLCHAR) {
8209                         SavePositionToFile(appData.savePositionFile);
8210                     }
8211                 }
8212             }
8213
8214             /* Tell program how game ended in case it is learning */
8215             /* [HGM] Moved this to after saving the PGN, just in case */
8216             /* engine died and we got here through time loss. In that */
8217             /* case we will get a fatal error writing the pipe, which */
8218             /* would otherwise lose us the PGN.                       */
8219             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8220             /* output during GameEnds should never be fatal anymore   */
8221             if (gameMode == MachinePlaysWhite ||
8222                 gameMode == MachinePlaysBlack ||
8223                 gameMode == TwoMachinesPlay ||
8224                 gameMode == IcsPlayingWhite ||
8225                 gameMode == IcsPlayingBlack ||
8226                 gameMode == BeginningOfGame) {
8227                 char buf[MSG_SIZ];
8228                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8229                         resultDetails);
8230                 if (first.pr != NoProc) {
8231                     SendToProgram(buf, &first);
8232                 }
8233                 if (second.pr != NoProc &&
8234                     gameMode == TwoMachinesPlay) {
8235                     SendToProgram(buf, &second);
8236                 }
8237             }
8238         }
8239
8240         if (appData.icsActive) {
8241             if (appData.quietPlay &&
8242                 (gameMode == IcsPlayingWhite ||
8243                  gameMode == IcsPlayingBlack)) {
8244                 SendToICS(ics_prefix);
8245                 SendToICS("set shout 1\n");
8246             }
8247             nextGameMode = IcsIdle;
8248             ics_user_moved = FALSE;
8249             /* clean up premove.  It's ugly when the game has ended and the
8250              * premove highlights are still on the board.
8251              */
8252             if (gotPremove) {
8253               gotPremove = FALSE;
8254               ClearPremoveHighlights();
8255               DrawPosition(FALSE, boards[currentMove]);
8256             }
8257             if (whosays == GE_ICS) {
8258                 switch (result) {
8259                 case WhiteWins:
8260                     if (gameMode == IcsPlayingWhite)
8261                         PlayIcsWinSound();
8262                     else if(gameMode == IcsPlayingBlack)
8263                         PlayIcsLossSound();
8264                     break;
8265                 case BlackWins:
8266                     if (gameMode == IcsPlayingBlack)
8267                         PlayIcsWinSound();
8268                     else if(gameMode == IcsPlayingWhite)
8269                         PlayIcsLossSound();
8270                     break;
8271                 case GameIsDrawn:
8272                     PlayIcsDrawSound();
8273                     break;
8274                 default:
8275                     PlayIcsUnfinishedSound();
8276                 }
8277             }
8278         } else if (gameMode == EditGame ||
8279                    gameMode == PlayFromGameFile || 
8280                    gameMode == AnalyzeMode || 
8281                    gameMode == AnalyzeFile) {
8282             nextGameMode = gameMode;
8283         } else {
8284             nextGameMode = EndOfGame;
8285         }
8286         pausing = FALSE;
8287         ModeHighlight();
8288     } else {
8289         nextGameMode = gameMode;
8290     }
8291
8292     if (appData.noChessProgram) {
8293         gameMode = nextGameMode;
8294         ModeHighlight();
8295         endingGame = 0; /* [HGM] crash */
8296         return;
8297     }
8298
8299     if (first.reuse) {
8300         /* Put first chess program into idle state */
8301         if (first.pr != NoProc &&
8302             (gameMode == MachinePlaysWhite ||
8303              gameMode == MachinePlaysBlack ||
8304              gameMode == TwoMachinesPlay ||
8305              gameMode == IcsPlayingWhite ||
8306              gameMode == IcsPlayingBlack ||
8307              gameMode == BeginningOfGame)) {
8308             SendToProgram("force\n", &first);
8309             if (first.usePing) {
8310               char buf[MSG_SIZ];
8311               sprintf(buf, "ping %d\n", ++first.lastPing);
8312               SendToProgram(buf, &first);
8313             }
8314         }
8315     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8316         /* Kill off first chess program */
8317         if (first.isr != NULL)
8318           RemoveInputSource(first.isr);
8319         first.isr = NULL;
8320     
8321         if (first.pr != NoProc) {
8322             ExitAnalyzeMode();
8323             DoSleep( appData.delayBeforeQuit );
8324             SendToProgram("quit\n", &first);
8325             DoSleep( appData.delayAfterQuit );
8326             DestroyChildProcess(first.pr, first.useSigterm);
8327         }
8328         first.pr = NoProc;
8329     }
8330     if (second.reuse) {
8331         /* Put second chess program into idle state */
8332         if (second.pr != NoProc &&
8333             gameMode == TwoMachinesPlay) {
8334             SendToProgram("force\n", &second);
8335             if (second.usePing) {
8336               char buf[MSG_SIZ];
8337               sprintf(buf, "ping %d\n", ++second.lastPing);
8338               SendToProgram(buf, &second);
8339             }
8340         }
8341     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8342         /* Kill off second chess program */
8343         if (second.isr != NULL)
8344           RemoveInputSource(second.isr);
8345         second.isr = NULL;
8346     
8347         if (second.pr != NoProc) {
8348             DoSleep( appData.delayBeforeQuit );
8349             SendToProgram("quit\n", &second);
8350             DoSleep( appData.delayAfterQuit );
8351             DestroyChildProcess(second.pr, second.useSigterm);
8352         }
8353         second.pr = NoProc;
8354     }
8355
8356     if (matchMode && gameMode == TwoMachinesPlay) {
8357         switch (result) {
8358         case WhiteWins:
8359           if (first.twoMachinesColor[0] == 'w') {
8360             first.matchWins++;
8361           } else {
8362             second.matchWins++;
8363           }
8364           break;
8365         case BlackWins:
8366           if (first.twoMachinesColor[0] == 'b') {
8367             first.matchWins++;
8368           } else {
8369             second.matchWins++;
8370           }
8371           break;
8372         default:
8373           break;
8374         }
8375         if (matchGame < appData.matchGames) {
8376             char *tmp;
8377             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8378                 tmp = first.twoMachinesColor;
8379                 first.twoMachinesColor = second.twoMachinesColor;
8380                 second.twoMachinesColor = tmp;
8381             }
8382             gameMode = nextGameMode;
8383             matchGame++;
8384             if(appData.matchPause>10000 || appData.matchPause<10)
8385                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8386             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8387             endingGame = 0; /* [HGM] crash */
8388             return;
8389         } else {
8390             char buf[MSG_SIZ];
8391             gameMode = nextGameMode;
8392             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8393                     first.tidy, second.tidy,
8394                     first.matchWins, second.matchWins,
8395                     appData.matchGames - (first.matchWins + second.matchWins));
8396             DisplayFatalError(buf, 0, 0);
8397         }
8398     }
8399     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8400         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8401       ExitAnalyzeMode();
8402     gameMode = nextGameMode;
8403     ModeHighlight();
8404     endingGame = 0;  /* [HGM] crash */
8405 }
8406
8407 /* Assumes program was just initialized (initString sent).
8408    Leaves program in force mode. */
8409 void
8410 FeedMovesToProgram(cps, upto) 
8411      ChessProgramState *cps;
8412      int upto;
8413 {
8414     int i;
8415     
8416     if (appData.debugMode)
8417       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8418               startedFromSetupPosition ? "position and " : "",
8419               backwardMostMove, upto, cps->which);
8420     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8421         // [HGM] variantswitch: make engine aware of new variant
8422         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8423                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8424         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8425         SendToProgram(buf, cps);
8426         currentlyInitializedVariant = gameInfo.variant;
8427     }
8428     SendToProgram("force\n", cps);
8429     if (startedFromSetupPosition) {
8430         SendBoard(cps, backwardMostMove);
8431     if (appData.debugMode) {
8432         fprintf(debugFP, "feedMoves\n");
8433     }
8434     }
8435     for (i = backwardMostMove; i < upto; i++) {
8436         SendMoveToProgram(i, cps);
8437     }
8438 }
8439
8440
8441 void
8442 ResurrectChessProgram()
8443 {
8444      /* The chess program may have exited.
8445         If so, restart it and feed it all the moves made so far. */
8446
8447     if (appData.noChessProgram || first.pr != NoProc) return;
8448     
8449     StartChessProgram(&first);
8450     InitChessProgram(&first, FALSE);
8451     FeedMovesToProgram(&first, currentMove);
8452
8453     if (!first.sendTime) {
8454         /* can't tell gnuchess what its clock should read,
8455            so we bow to its notion. */
8456         ResetClocks();
8457         timeRemaining[0][currentMove] = whiteTimeRemaining;
8458         timeRemaining[1][currentMove] = blackTimeRemaining;
8459     }
8460
8461     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8462                 appData.icsEngineAnalyze) && first.analysisSupport) {
8463       SendToProgram("analyze\n", &first);
8464       first.analyzing = TRUE;
8465     }
8466 }
8467
8468 /*
8469  * Button procedures
8470  */
8471 void
8472 Reset(redraw, init)
8473      int redraw, init;
8474 {
8475     int i;
8476
8477     if (appData.debugMode) {
8478         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8479                 redraw, init, gameMode);
8480     }
8481     CleanupTail(); // [HGM] vari: delete any stored variations
8482     pausing = pauseExamInvalid = FALSE;
8483     startedFromSetupPosition = blackPlaysFirst = FALSE;
8484     firstMove = TRUE;
8485     whiteFlag = blackFlag = FALSE;
8486     userOfferedDraw = FALSE;
8487     hintRequested = bookRequested = FALSE;
8488     first.maybeThinking = FALSE;
8489     second.maybeThinking = FALSE;
8490     first.bookSuspend = FALSE; // [HGM] book
8491     second.bookSuspend = FALSE;
8492     thinkOutput[0] = NULLCHAR;
8493     lastHint[0] = NULLCHAR;
8494     ClearGameInfo(&gameInfo);
8495     gameInfo.variant = StringToVariant(appData.variant);
8496     ics_user_moved = ics_clock_paused = FALSE;
8497     ics_getting_history = H_FALSE;
8498     ics_gamenum = -1;
8499     white_holding[0] = black_holding[0] = NULLCHAR;
8500     ClearProgramStats();
8501     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8502     
8503     ResetFrontEnd();
8504     ClearHighlights();
8505     flipView = appData.flipView;
8506     ClearPremoveHighlights();
8507     gotPremove = FALSE;
8508     alarmSounded = FALSE;
8509
8510     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8511     if(appData.serverMovesName != NULL) {
8512         /* [HGM] prepare to make moves file for broadcasting */
8513         clock_t t = clock();
8514         if(serverMoves != NULL) fclose(serverMoves);
8515         serverMoves = fopen(appData.serverMovesName, "r");
8516         if(serverMoves != NULL) {
8517             fclose(serverMoves);
8518             /* delay 15 sec before overwriting, so all clients can see end */
8519             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8520         }
8521         serverMoves = fopen(appData.serverMovesName, "w");
8522     }
8523
8524     ExitAnalyzeMode();
8525     gameMode = BeginningOfGame;
8526     ModeHighlight();
8527     if(appData.icsActive) gameInfo.variant = VariantNormal;
8528     currentMove = forwardMostMove = backwardMostMove = 0;
8529     InitPosition(redraw);
8530     for (i = 0; i < MAX_MOVES; i++) {
8531         if (commentList[i] != NULL) {
8532             free(commentList[i]);
8533             commentList[i] = NULL;
8534         }
8535     }
8536     ResetClocks();
8537     timeRemaining[0][0] = whiteTimeRemaining;
8538     timeRemaining[1][0] = blackTimeRemaining;
8539     if (first.pr == NULL) {
8540         StartChessProgram(&first);
8541     }
8542     if (init) {
8543             InitChessProgram(&first, startedFromSetupPosition);
8544     }
8545     DisplayTitle("");
8546     DisplayMessage("", "");
8547     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8548     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8549 }
8550
8551 void
8552 AutoPlayGameLoop()
8553 {
8554     for (;;) {
8555         if (!AutoPlayOneMove())
8556           return;
8557         if (matchMode || appData.timeDelay == 0)
8558           continue;
8559         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8560           return;
8561         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8562         break;
8563     }
8564 }
8565
8566
8567 int
8568 AutoPlayOneMove()
8569 {
8570     int fromX, fromY, toX, toY;
8571
8572     if (appData.debugMode) {
8573       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8574     }
8575
8576     if (gameMode != PlayFromGameFile)
8577       return FALSE;
8578
8579     if (currentMove >= forwardMostMove) {
8580       gameMode = EditGame;
8581       ModeHighlight();
8582
8583       /* [AS] Clear current move marker at the end of a game */
8584       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8585
8586       return FALSE;
8587     }
8588     
8589     toX = moveList[currentMove][2] - AAA;
8590     toY = moveList[currentMove][3] - ONE;
8591
8592     if (moveList[currentMove][1] == '@') {
8593         if (appData.highlightLastMove) {
8594             SetHighlights(-1, -1, toX, toY);
8595         }
8596     } else {
8597         fromX = moveList[currentMove][0] - AAA;
8598         fromY = moveList[currentMove][1] - ONE;
8599
8600         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8601
8602         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8603
8604         if (appData.highlightLastMove) {
8605             SetHighlights(fromX, fromY, toX, toY);
8606         }
8607     }
8608     DisplayMove(currentMove);
8609     SendMoveToProgram(currentMove++, &first);
8610     DisplayBothClocks();
8611     DrawPosition(FALSE, boards[currentMove]);
8612     // [HGM] PV info: always display, routine tests if empty
8613     DisplayComment(currentMove - 1, commentList[currentMove]);
8614     return TRUE;
8615 }
8616
8617
8618 int
8619 LoadGameOneMove(readAhead)
8620      ChessMove readAhead;
8621 {
8622     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8623     char promoChar = NULLCHAR;
8624     ChessMove moveType;
8625     char move[MSG_SIZ];
8626     char *p, *q;
8627     
8628     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8629         gameMode != AnalyzeMode && gameMode != Training) {
8630         gameFileFP = NULL;
8631         return FALSE;
8632     }
8633     
8634     yyboardindex = forwardMostMove;
8635     if (readAhead != (ChessMove)0) {
8636       moveType = readAhead;
8637     } else {
8638       if (gameFileFP == NULL)
8639           return FALSE;
8640       moveType = (ChessMove) yylex();
8641     }
8642     
8643     done = FALSE;
8644     switch (moveType) {
8645       case Comment:
8646         if (appData.debugMode) 
8647           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8648         p = yy_text;
8649
8650         /* append the comment but don't display it */
8651         AppendComment(currentMove, p, FALSE);
8652         return TRUE;
8653
8654       case WhiteCapturesEnPassant:
8655       case BlackCapturesEnPassant:
8656       case WhitePromotionChancellor:
8657       case BlackPromotionChancellor:
8658       case WhitePromotionArchbishop:
8659       case BlackPromotionArchbishop:
8660       case WhitePromotionCentaur:
8661       case BlackPromotionCentaur:
8662       case WhitePromotionQueen:
8663       case BlackPromotionQueen:
8664       case WhitePromotionRook:
8665       case BlackPromotionRook:
8666       case WhitePromotionBishop:
8667       case BlackPromotionBishop:
8668       case WhitePromotionKnight:
8669       case BlackPromotionKnight:
8670       case WhitePromotionKing:
8671       case BlackPromotionKing:
8672       case NormalMove:
8673       case WhiteKingSideCastle:
8674       case WhiteQueenSideCastle:
8675       case BlackKingSideCastle:
8676       case BlackQueenSideCastle:
8677       case WhiteKingSideCastleWild:
8678       case WhiteQueenSideCastleWild:
8679       case BlackKingSideCastleWild:
8680       case BlackQueenSideCastleWild:
8681       /* PUSH Fabien */
8682       case WhiteHSideCastleFR:
8683       case WhiteASideCastleFR:
8684       case BlackHSideCastleFR:
8685       case BlackASideCastleFR:
8686       /* POP Fabien */
8687         if (appData.debugMode)
8688           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8689         fromX = currentMoveString[0] - AAA;
8690         fromY = currentMoveString[1] - ONE;
8691         toX = currentMoveString[2] - AAA;
8692         toY = currentMoveString[3] - ONE;
8693         promoChar = currentMoveString[4];
8694         break;
8695
8696       case WhiteDrop:
8697       case BlackDrop:
8698         if (appData.debugMode)
8699           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8700         fromX = moveType == WhiteDrop ?
8701           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8702         (int) CharToPiece(ToLower(currentMoveString[0]));
8703         fromY = DROP_RANK;
8704         toX = currentMoveString[2] - AAA;
8705         toY = currentMoveString[3] - ONE;
8706         break;
8707
8708       case WhiteWins:
8709       case BlackWins:
8710       case GameIsDrawn:
8711       case GameUnfinished:
8712         if (appData.debugMode)
8713           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8714         p = strchr(yy_text, '{');
8715         if (p == NULL) p = strchr(yy_text, '(');
8716         if (p == NULL) {
8717             p = yy_text;
8718             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8719         } else {
8720             q = strchr(p, *p == '{' ? '}' : ')');
8721             if (q != NULL) *q = NULLCHAR;
8722             p++;
8723         }
8724         GameEnds(moveType, p, GE_FILE);
8725         done = TRUE;
8726         if (cmailMsgLoaded) {
8727             ClearHighlights();
8728             flipView = WhiteOnMove(currentMove);
8729             if (moveType == GameUnfinished) flipView = !flipView;
8730             if (appData.debugMode)
8731               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8732         }
8733         break;
8734
8735       case (ChessMove) 0:       /* end of file */
8736         if (appData.debugMode)
8737           fprintf(debugFP, "Parser hit end of file\n");
8738         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8739           case MT_NONE:
8740           case MT_CHECK:
8741             break;
8742           case MT_CHECKMATE:
8743           case MT_STAINMATE:
8744             if (WhiteOnMove(currentMove)) {
8745                 GameEnds(BlackWins, "Black mates", GE_FILE);
8746             } else {
8747                 GameEnds(WhiteWins, "White mates", GE_FILE);
8748             }
8749             break;
8750           case MT_STALEMATE:
8751             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8752             break;
8753         }
8754         done = TRUE;
8755         break;
8756
8757       case MoveNumberOne:
8758         if (lastLoadGameStart == GNUChessGame) {
8759             /* GNUChessGames have numbers, but they aren't move numbers */
8760             if (appData.debugMode)
8761               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8762                       yy_text, (int) moveType);
8763             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8764         }
8765         /* else fall thru */
8766
8767       case XBoardGame:
8768       case GNUChessGame:
8769       case PGNTag:
8770         /* Reached start of next game in file */
8771         if (appData.debugMode)
8772           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8773         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8774           case MT_NONE:
8775           case MT_CHECK:
8776             break;
8777           case MT_CHECKMATE:
8778           case MT_STAINMATE:
8779             if (WhiteOnMove(currentMove)) {
8780                 GameEnds(BlackWins, "Black mates", GE_FILE);
8781             } else {
8782                 GameEnds(WhiteWins, "White mates", GE_FILE);
8783             }
8784             break;
8785           case MT_STALEMATE:
8786             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8787             break;
8788         }
8789         done = TRUE;
8790         break;
8791
8792       case PositionDiagram:     /* should not happen; ignore */
8793       case ElapsedTime:         /* ignore */
8794       case NAG:                 /* ignore */
8795         if (appData.debugMode)
8796           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8797                   yy_text, (int) moveType);
8798         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8799
8800       case IllegalMove:
8801         if (appData.testLegality) {
8802             if (appData.debugMode)
8803               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8804             sprintf(move, _("Illegal move: %d.%s%s"),
8805                     (forwardMostMove / 2) + 1,
8806                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8807             DisplayError(move, 0);
8808             done = TRUE;
8809         } else {
8810             if (appData.debugMode)
8811               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8812                       yy_text, currentMoveString);
8813             fromX = currentMoveString[0] - AAA;
8814             fromY = currentMoveString[1] - ONE;
8815             toX = currentMoveString[2] - AAA;
8816             toY = currentMoveString[3] - ONE;
8817             promoChar = currentMoveString[4];
8818         }
8819         break;
8820
8821       case AmbiguousMove:
8822         if (appData.debugMode)
8823           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8824         sprintf(move, _("Ambiguous move: %d.%s%s"),
8825                 (forwardMostMove / 2) + 1,
8826                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8827         DisplayError(move, 0);
8828         done = TRUE;
8829         break;
8830
8831       default:
8832       case ImpossibleMove:
8833         if (appData.debugMode)
8834           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8835         sprintf(move, _("Illegal move: %d.%s%s"),
8836                 (forwardMostMove / 2) + 1,
8837                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8838         DisplayError(move, 0);
8839         done = TRUE;
8840         break;
8841     }
8842
8843     if (done) {
8844         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8845             DrawPosition(FALSE, boards[currentMove]);
8846             DisplayBothClocks();
8847             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8848               DisplayComment(currentMove - 1, commentList[currentMove]);
8849         }
8850         (void) StopLoadGameTimer();
8851         gameFileFP = NULL;
8852         cmailOldMove = forwardMostMove;
8853         return FALSE;
8854     } else {
8855         /* currentMoveString is set as a side-effect of yylex */
8856         strcat(currentMoveString, "\n");
8857         strcpy(moveList[forwardMostMove], currentMoveString);
8858         
8859         thinkOutput[0] = NULLCHAR;
8860         MakeMove(fromX, fromY, toX, toY, promoChar);
8861         currentMove = forwardMostMove;
8862         return TRUE;
8863     }
8864 }
8865
8866 /* Load the nth game from the given file */
8867 int
8868 LoadGameFromFile(filename, n, title, useList)
8869      char *filename;
8870      int n;
8871      char *title;
8872      /*Boolean*/ int useList;
8873 {
8874     FILE *f;
8875     char buf[MSG_SIZ];
8876
8877     if (strcmp(filename, "-") == 0) {
8878         f = stdin;
8879         title = "stdin";
8880     } else {
8881         f = fopen(filename, "rb");
8882         if (f == NULL) {
8883           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8884             DisplayError(buf, errno);
8885             return FALSE;
8886         }
8887     }
8888     if (fseek(f, 0, 0) == -1) {
8889         /* f is not seekable; probably a pipe */
8890         useList = FALSE;
8891     }
8892     if (useList && n == 0) {
8893         int error = GameListBuild(f);
8894         if (error) {
8895             DisplayError(_("Cannot build game list"), error);
8896         } else if (!ListEmpty(&gameList) &&
8897                    ((ListGame *) gameList.tailPred)->number > 1) {
8898             GameListPopUp(f, title);
8899             return TRUE;
8900         }
8901         GameListDestroy();
8902         n = 1;
8903     }
8904     if (n == 0) n = 1;
8905     return LoadGame(f, n, title, FALSE);
8906 }
8907
8908
8909 void
8910 MakeRegisteredMove()
8911 {
8912     int fromX, fromY, toX, toY;
8913     char promoChar;
8914     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8915         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8916           case CMAIL_MOVE:
8917           case CMAIL_DRAW:
8918             if (appData.debugMode)
8919               fprintf(debugFP, "Restoring %s for game %d\n",
8920                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8921     
8922             thinkOutput[0] = NULLCHAR;
8923             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8924             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8925             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8926             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8927             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8928             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8929             MakeMove(fromX, fromY, toX, toY, promoChar);
8930             ShowMove(fromX, fromY, toX, toY);
8931               
8932             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8933               case MT_NONE:
8934               case MT_CHECK:
8935                 break;
8936                 
8937               case MT_CHECKMATE:
8938               case MT_STAINMATE:
8939                 if (WhiteOnMove(currentMove)) {
8940                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8941                 } else {
8942                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8943                 }
8944                 break;
8945                 
8946               case MT_STALEMATE:
8947                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8948                 break;
8949             }
8950
8951             break;
8952             
8953           case CMAIL_RESIGN:
8954             if (WhiteOnMove(currentMove)) {
8955                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8956             } else {
8957                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8958             }
8959             break;
8960             
8961           case CMAIL_ACCEPT:
8962             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8963             break;
8964               
8965           default:
8966             break;
8967         }
8968     }
8969
8970     return;
8971 }
8972
8973 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8974 int
8975 CmailLoadGame(f, gameNumber, title, useList)
8976      FILE *f;
8977      int gameNumber;
8978      char *title;
8979      int useList;
8980 {
8981     int retVal;
8982
8983     if (gameNumber > nCmailGames) {
8984         DisplayError(_("No more games in this message"), 0);
8985         return FALSE;
8986     }
8987     if (f == lastLoadGameFP) {
8988         int offset = gameNumber - lastLoadGameNumber;
8989         if (offset == 0) {
8990             cmailMsg[0] = NULLCHAR;
8991             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8992                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8993                 nCmailMovesRegistered--;
8994             }
8995             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8996             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8997                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8998             }
8999         } else {
9000             if (! RegisterMove()) return FALSE;
9001         }
9002     }
9003
9004     retVal = LoadGame(f, gameNumber, title, useList);
9005
9006     /* Make move registered during previous look at this game, if any */
9007     MakeRegisteredMove();
9008
9009     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9010         commentList[currentMove]
9011           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9012         DisplayComment(currentMove - 1, commentList[currentMove]);
9013     }
9014
9015     return retVal;
9016 }
9017
9018 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9019 int
9020 ReloadGame(offset)
9021      int offset;
9022 {
9023     int gameNumber = lastLoadGameNumber + offset;
9024     if (lastLoadGameFP == NULL) {
9025         DisplayError(_("No game has been loaded yet"), 0);
9026         return FALSE;
9027     }
9028     if (gameNumber <= 0) {
9029         DisplayError(_("Can't back up any further"), 0);
9030         return FALSE;
9031     }
9032     if (cmailMsgLoaded) {
9033         return CmailLoadGame(lastLoadGameFP, gameNumber,
9034                              lastLoadGameTitle, lastLoadGameUseList);
9035     } else {
9036         return LoadGame(lastLoadGameFP, gameNumber,
9037                         lastLoadGameTitle, lastLoadGameUseList);
9038     }
9039 }
9040
9041
9042
9043 /* Load the nth game from open file f */
9044 int
9045 LoadGame(f, gameNumber, title, useList)
9046      FILE *f;
9047      int gameNumber;
9048      char *title;
9049      int useList;
9050 {
9051     ChessMove cm;
9052     char buf[MSG_SIZ];
9053     int gn = gameNumber;
9054     ListGame *lg = NULL;
9055     int numPGNTags = 0;
9056     int err;
9057     GameMode oldGameMode;
9058     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9059
9060     if (appData.debugMode) 
9061         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9062
9063     if (gameMode == Training )
9064         SetTrainingModeOff();
9065
9066     oldGameMode = gameMode;
9067     if (gameMode != BeginningOfGame) {
9068       Reset(FALSE, TRUE);
9069     }
9070
9071     gameFileFP = f;
9072     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9073         fclose(lastLoadGameFP);
9074     }
9075
9076     if (useList) {
9077         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9078         
9079         if (lg) {
9080             fseek(f, lg->offset, 0);
9081             GameListHighlight(gameNumber);
9082             gn = 1;
9083         }
9084         else {
9085             DisplayError(_("Game number out of range"), 0);
9086             return FALSE;
9087         }
9088     } else {
9089         GameListDestroy();
9090         if (fseek(f, 0, 0) == -1) {
9091             if (f == lastLoadGameFP ?
9092                 gameNumber == lastLoadGameNumber + 1 :
9093                 gameNumber == 1) {
9094                 gn = 1;
9095             } else {
9096                 DisplayError(_("Can't seek on game file"), 0);
9097                 return FALSE;
9098             }
9099         }
9100     }
9101     lastLoadGameFP = f;
9102     lastLoadGameNumber = gameNumber;
9103     strcpy(lastLoadGameTitle, title);
9104     lastLoadGameUseList = useList;
9105
9106     yynewfile(f);
9107
9108     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9109       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9110                 lg->gameInfo.black);
9111             DisplayTitle(buf);
9112     } else if (*title != NULLCHAR) {
9113         if (gameNumber > 1) {
9114             sprintf(buf, "%s %d", title, gameNumber);
9115             DisplayTitle(buf);
9116         } else {
9117             DisplayTitle(title);
9118         }
9119     }
9120
9121     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9122         gameMode = PlayFromGameFile;
9123         ModeHighlight();
9124     }
9125
9126     currentMove = forwardMostMove = backwardMostMove = 0;
9127     CopyBoard(boards[0], initialPosition);
9128     StopClocks();
9129
9130     /*
9131      * Skip the first gn-1 games in the file.
9132      * Also skip over anything that precedes an identifiable 
9133      * start of game marker, to avoid being confused by 
9134      * garbage at the start of the file.  Currently 
9135      * recognized start of game markers are the move number "1",
9136      * the pattern "gnuchess .* game", the pattern
9137      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9138      * A game that starts with one of the latter two patterns
9139      * will also have a move number 1, possibly
9140      * following a position diagram.
9141      * 5-4-02: Let's try being more lenient and allowing a game to
9142      * start with an unnumbered move.  Does that break anything?
9143      */
9144     cm = lastLoadGameStart = (ChessMove) 0;
9145     while (gn > 0) {
9146         yyboardindex = forwardMostMove;
9147         cm = (ChessMove) yylex();
9148         switch (cm) {
9149           case (ChessMove) 0:
9150             if (cmailMsgLoaded) {
9151                 nCmailGames = CMAIL_MAX_GAMES - gn;
9152             } else {
9153                 Reset(TRUE, TRUE);
9154                 DisplayError(_("Game not found in file"), 0);
9155             }
9156             return FALSE;
9157
9158           case GNUChessGame:
9159           case XBoardGame:
9160             gn--;
9161             lastLoadGameStart = cm;
9162             break;
9163             
9164           case MoveNumberOne:
9165             switch (lastLoadGameStart) {
9166               case GNUChessGame:
9167               case XBoardGame:
9168               case PGNTag:
9169                 break;
9170               case MoveNumberOne:
9171               case (ChessMove) 0:
9172                 gn--;           /* count this game */
9173                 lastLoadGameStart = cm;
9174                 break;
9175               default:
9176                 /* impossible */
9177                 break;
9178             }
9179             break;
9180
9181           case PGNTag:
9182             switch (lastLoadGameStart) {
9183               case GNUChessGame:
9184               case PGNTag:
9185               case MoveNumberOne:
9186               case (ChessMove) 0:
9187                 gn--;           /* count this game */
9188                 lastLoadGameStart = cm;
9189                 break;
9190               case XBoardGame:
9191                 lastLoadGameStart = cm; /* game counted already */
9192                 break;
9193               default:
9194                 /* impossible */
9195                 break;
9196             }
9197             if (gn > 0) {
9198                 do {
9199                     yyboardindex = forwardMostMove;
9200                     cm = (ChessMove) yylex();
9201                 } while (cm == PGNTag || cm == Comment);
9202             }
9203             break;
9204
9205           case WhiteWins:
9206           case BlackWins:
9207           case GameIsDrawn:
9208             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9209                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9210                     != CMAIL_OLD_RESULT) {
9211                     nCmailResults ++ ;
9212                     cmailResult[  CMAIL_MAX_GAMES
9213                                 - gn - 1] = CMAIL_OLD_RESULT;
9214                 }
9215             }
9216             break;
9217
9218           case NormalMove:
9219             /* Only a NormalMove can be at the start of a game
9220              * without a position diagram. */
9221             if (lastLoadGameStart == (ChessMove) 0) {
9222               gn--;
9223               lastLoadGameStart = MoveNumberOne;
9224             }
9225             break;
9226
9227           default:
9228             break;
9229         }
9230     }
9231     
9232     if (appData.debugMode)
9233       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9234
9235     if (cm == XBoardGame) {
9236         /* Skip any header junk before position diagram and/or move 1 */
9237         for (;;) {
9238             yyboardindex = forwardMostMove;
9239             cm = (ChessMove) yylex();
9240
9241             if (cm == (ChessMove) 0 ||
9242                 cm == GNUChessGame || cm == XBoardGame) {
9243                 /* Empty game; pretend end-of-file and handle later */
9244                 cm = (ChessMove) 0;
9245                 break;
9246             }
9247
9248             if (cm == MoveNumberOne || cm == PositionDiagram ||
9249                 cm == PGNTag || cm == Comment)
9250               break;
9251         }
9252     } else if (cm == GNUChessGame) {
9253         if (gameInfo.event != NULL) {
9254             free(gameInfo.event);
9255         }
9256         gameInfo.event = StrSave(yy_text);
9257     }   
9258
9259     startedFromSetupPosition = FALSE;
9260     while (cm == PGNTag) {
9261         if (appData.debugMode) 
9262           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9263         err = ParsePGNTag(yy_text, &gameInfo);
9264         if (!err) numPGNTags++;
9265
9266         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9267         if(gameInfo.variant != oldVariant) {
9268             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9269             InitPosition(TRUE);
9270             oldVariant = gameInfo.variant;
9271             if (appData.debugMode) 
9272               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9273         }
9274
9275
9276         if (gameInfo.fen != NULL) {
9277           Board initial_position;
9278           startedFromSetupPosition = TRUE;
9279           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9280             Reset(TRUE, TRUE);
9281             DisplayError(_("Bad FEN position in file"), 0);
9282             return FALSE;
9283           }
9284           CopyBoard(boards[0], initial_position);
9285           if (blackPlaysFirst) {
9286             currentMove = forwardMostMove = backwardMostMove = 1;
9287             CopyBoard(boards[1], initial_position);
9288             strcpy(moveList[0], "");
9289             strcpy(parseList[0], "");
9290             timeRemaining[0][1] = whiteTimeRemaining;
9291             timeRemaining[1][1] = blackTimeRemaining;
9292             if (commentList[0] != NULL) {
9293               commentList[1] = commentList[0];
9294               commentList[0] = NULL;
9295             }
9296           } else {
9297             currentMove = forwardMostMove = backwardMostMove = 0;
9298           }
9299           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9300           {   int i;
9301               initialRulePlies = FENrulePlies;
9302               for( i=0; i< nrCastlingRights; i++ )
9303                   initialRights[i] = initial_position[CASTLING][i];
9304           }
9305           yyboardindex = forwardMostMove;
9306           free(gameInfo.fen);
9307           gameInfo.fen = NULL;
9308         }
9309
9310         yyboardindex = forwardMostMove;
9311         cm = (ChessMove) yylex();
9312
9313         /* Handle comments interspersed among the tags */
9314         while (cm == Comment) {
9315             char *p;
9316             if (appData.debugMode) 
9317               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9318             p = yy_text;
9319             AppendComment(currentMove, p, FALSE);
9320             yyboardindex = forwardMostMove;
9321             cm = (ChessMove) yylex();
9322         }
9323     }
9324
9325     /* don't rely on existence of Event tag since if game was
9326      * pasted from clipboard the Event tag may not exist
9327      */
9328     if (numPGNTags > 0){
9329         char *tags;
9330         if (gameInfo.variant == VariantNormal) {
9331           gameInfo.variant = StringToVariant(gameInfo.event);
9332         }
9333         if (!matchMode) {
9334           if( appData.autoDisplayTags ) {
9335             tags = PGNTags(&gameInfo);
9336             TagsPopUp(tags, CmailMsg());
9337             free(tags);
9338           }
9339         }
9340     } else {
9341         /* Make something up, but don't display it now */
9342         SetGameInfo();
9343         TagsPopDown();
9344     }
9345
9346     if (cm == PositionDiagram) {
9347         int i, j;
9348         char *p;
9349         Board initial_position;
9350
9351         if (appData.debugMode)
9352           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9353
9354         if (!startedFromSetupPosition) {
9355             p = yy_text;
9356             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9357               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9358                 switch (*p) {
9359                   case '[':
9360                   case '-':
9361                   case ' ':
9362                   case '\t':
9363                   case '\n':
9364                   case '\r':
9365                     break;
9366                   default:
9367                     initial_position[i][j++] = CharToPiece(*p);
9368                     break;
9369                 }
9370             while (*p == ' ' || *p == '\t' ||
9371                    *p == '\n' || *p == '\r') p++;
9372         
9373             if (strncmp(p, "black", strlen("black"))==0)
9374               blackPlaysFirst = TRUE;
9375             else
9376               blackPlaysFirst = FALSE;
9377             startedFromSetupPosition = TRUE;
9378         
9379             CopyBoard(boards[0], initial_position);
9380             if (blackPlaysFirst) {
9381                 currentMove = forwardMostMove = backwardMostMove = 1;
9382                 CopyBoard(boards[1], initial_position);
9383                 strcpy(moveList[0], "");
9384                 strcpy(parseList[0], "");
9385                 timeRemaining[0][1] = whiteTimeRemaining;
9386                 timeRemaining[1][1] = blackTimeRemaining;
9387                 if (commentList[0] != NULL) {
9388                     commentList[1] = commentList[0];
9389                     commentList[0] = NULL;
9390                 }
9391             } else {
9392                 currentMove = forwardMostMove = backwardMostMove = 0;
9393             }
9394         }
9395         yyboardindex = forwardMostMove;
9396         cm = (ChessMove) yylex();
9397     }
9398
9399     if (first.pr == NoProc) {
9400         StartChessProgram(&first);
9401     }
9402     InitChessProgram(&first, FALSE);
9403     SendToProgram("force\n", &first);
9404     if (startedFromSetupPosition) {
9405         SendBoard(&first, forwardMostMove);
9406     if (appData.debugMode) {
9407         fprintf(debugFP, "Load Game\n");
9408     }
9409         DisplayBothClocks();
9410     }      
9411
9412     /* [HGM] server: flag to write setup moves in broadcast file as one */
9413     loadFlag = appData.suppressLoadMoves;
9414
9415     while (cm == Comment) {
9416         char *p;
9417         if (appData.debugMode) 
9418           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9419         p = yy_text;
9420         AppendComment(currentMove, p, FALSE);
9421         yyboardindex = forwardMostMove;
9422         cm = (ChessMove) yylex();
9423     }
9424
9425     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9426         cm == WhiteWins || cm == BlackWins ||
9427         cm == GameIsDrawn || cm == GameUnfinished) {
9428         DisplayMessage("", _("No moves in game"));
9429         if (cmailMsgLoaded) {
9430             if (appData.debugMode)
9431               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9432             ClearHighlights();
9433             flipView = FALSE;
9434         }
9435         DrawPosition(FALSE, boards[currentMove]);
9436         DisplayBothClocks();
9437         gameMode = EditGame;
9438         ModeHighlight();
9439         gameFileFP = NULL;
9440         cmailOldMove = 0;
9441         return TRUE;
9442     }
9443
9444     // [HGM] PV info: routine tests if comment empty
9445     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9446         DisplayComment(currentMove - 1, commentList[currentMove]);
9447     }
9448     if (!matchMode && appData.timeDelay != 0) 
9449       DrawPosition(FALSE, boards[currentMove]);
9450
9451     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9452       programStats.ok_to_send = 1;
9453     }
9454
9455     /* if the first token after the PGN tags is a move
9456      * and not move number 1, retrieve it from the parser 
9457      */
9458     if (cm != MoveNumberOne)
9459         LoadGameOneMove(cm);
9460
9461     /* load the remaining moves from the file */
9462     while (LoadGameOneMove((ChessMove)0)) {
9463       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9464       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9465     }
9466
9467     /* rewind to the start of the game */
9468     currentMove = backwardMostMove;
9469
9470     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9471
9472     if (oldGameMode == AnalyzeFile ||
9473         oldGameMode == AnalyzeMode) {
9474       AnalyzeFileEvent();
9475     }
9476
9477     if (matchMode || appData.timeDelay == 0) {
9478       ToEndEvent();
9479       gameMode = EditGame;
9480       ModeHighlight();
9481     } else if (appData.timeDelay > 0) {
9482       AutoPlayGameLoop();
9483     }
9484
9485     if (appData.debugMode) 
9486         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9487
9488     loadFlag = 0; /* [HGM] true game starts */
9489     return TRUE;
9490 }
9491
9492 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9493 int
9494 ReloadPosition(offset)
9495      int offset;
9496 {
9497     int positionNumber = lastLoadPositionNumber + offset;
9498     if (lastLoadPositionFP == NULL) {
9499         DisplayError(_("No position has been loaded yet"), 0);
9500         return FALSE;
9501     }
9502     if (positionNumber <= 0) {
9503         DisplayError(_("Can't back up any further"), 0);
9504         return FALSE;
9505     }
9506     return LoadPosition(lastLoadPositionFP, positionNumber,
9507                         lastLoadPositionTitle);
9508 }
9509
9510 /* Load the nth position from the given file */
9511 int
9512 LoadPositionFromFile(filename, n, title)
9513      char *filename;
9514      int n;
9515      char *title;
9516 {
9517     FILE *f;
9518     char buf[MSG_SIZ];
9519
9520     if (strcmp(filename, "-") == 0) {
9521         return LoadPosition(stdin, n, "stdin");
9522     } else {
9523         f = fopen(filename, "rb");
9524         if (f == NULL) {
9525             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9526             DisplayError(buf, errno);
9527             return FALSE;
9528         } else {
9529             return LoadPosition(f, n, title);
9530         }
9531     }
9532 }
9533
9534 /* Load the nth position from the given open file, and close it */
9535 int
9536 LoadPosition(f, positionNumber, title)
9537      FILE *f;
9538      int positionNumber;
9539      char *title;
9540 {
9541     char *p, line[MSG_SIZ];
9542     Board initial_position;
9543     int i, j, fenMode, pn;
9544     
9545     if (gameMode == Training )
9546         SetTrainingModeOff();
9547
9548     if (gameMode != BeginningOfGame) {
9549         Reset(FALSE, TRUE);
9550     }
9551     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9552         fclose(lastLoadPositionFP);
9553     }
9554     if (positionNumber == 0) positionNumber = 1;
9555     lastLoadPositionFP = f;
9556     lastLoadPositionNumber = positionNumber;
9557     strcpy(lastLoadPositionTitle, title);
9558     if (first.pr == NoProc) {
9559       StartChessProgram(&first);
9560       InitChessProgram(&first, FALSE);
9561     }    
9562     pn = positionNumber;
9563     if (positionNumber < 0) {
9564         /* Negative position number means to seek to that byte offset */
9565         if (fseek(f, -positionNumber, 0) == -1) {
9566             DisplayError(_("Can't seek on position file"), 0);
9567             return FALSE;
9568         };
9569         pn = 1;
9570     } else {
9571         if (fseek(f, 0, 0) == -1) {
9572             if (f == lastLoadPositionFP ?
9573                 positionNumber == lastLoadPositionNumber + 1 :
9574                 positionNumber == 1) {
9575                 pn = 1;
9576             } else {
9577                 DisplayError(_("Can't seek on position file"), 0);
9578                 return FALSE;
9579             }
9580         }
9581     }
9582     /* See if this file is FEN or old-style xboard */
9583     if (fgets(line, MSG_SIZ, f) == NULL) {
9584         DisplayError(_("Position not found in file"), 0);
9585         return FALSE;
9586     }
9587     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9588     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9589
9590     if (pn >= 2) {
9591         if (fenMode || line[0] == '#') pn--;
9592         while (pn > 0) {
9593             /* skip positions before number pn */
9594             if (fgets(line, MSG_SIZ, f) == NULL) {
9595                 Reset(TRUE, TRUE);
9596                 DisplayError(_("Position not found in file"), 0);
9597                 return FALSE;
9598             }
9599             if (fenMode || line[0] == '#') pn--;
9600         }
9601     }
9602
9603     if (fenMode) {
9604         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9605             DisplayError(_("Bad FEN position in file"), 0);
9606             return FALSE;
9607         }
9608     } else {
9609         (void) fgets(line, MSG_SIZ, f);
9610         (void) fgets(line, MSG_SIZ, f);
9611     
9612         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9613             (void) fgets(line, MSG_SIZ, f);
9614             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9615                 if (*p == ' ')
9616                   continue;
9617                 initial_position[i][j++] = CharToPiece(*p);
9618             }
9619         }
9620     
9621         blackPlaysFirst = FALSE;
9622         if (!feof(f)) {
9623             (void) fgets(line, MSG_SIZ, f);
9624             if (strncmp(line, "black", strlen("black"))==0)
9625               blackPlaysFirst = TRUE;
9626         }
9627     }
9628     startedFromSetupPosition = TRUE;
9629     
9630     SendToProgram("force\n", &first);
9631     CopyBoard(boards[0], initial_position);
9632     if (blackPlaysFirst) {
9633         currentMove = forwardMostMove = backwardMostMove = 1;
9634         strcpy(moveList[0], "");
9635         strcpy(parseList[0], "");
9636         CopyBoard(boards[1], initial_position);
9637         DisplayMessage("", _("Black to play"));
9638     } else {
9639         currentMove = forwardMostMove = backwardMostMove = 0;
9640         DisplayMessage("", _("White to play"));
9641     }
9642     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9643     SendBoard(&first, forwardMostMove);
9644     if (appData.debugMode) {
9645 int i, j;
9646   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9647   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9648         fprintf(debugFP, "Load Position\n");
9649     }
9650
9651     if (positionNumber > 1) {
9652         sprintf(line, "%s %d", title, positionNumber);
9653         DisplayTitle(line);
9654     } else {
9655         DisplayTitle(title);
9656     }
9657     gameMode = EditGame;
9658     ModeHighlight();
9659     ResetClocks();
9660     timeRemaining[0][1] = whiteTimeRemaining;
9661     timeRemaining[1][1] = blackTimeRemaining;
9662     DrawPosition(FALSE, boards[currentMove]);
9663    
9664     return TRUE;
9665 }
9666
9667
9668 void
9669 CopyPlayerNameIntoFileName(dest, src)
9670      char **dest, *src;
9671 {
9672     while (*src != NULLCHAR && *src != ',') {
9673         if (*src == ' ') {
9674             *(*dest)++ = '_';
9675             src++;
9676         } else {
9677             *(*dest)++ = *src++;
9678         }
9679     }
9680 }
9681
9682 char *DefaultFileName(ext)
9683      char *ext;
9684 {
9685     static char def[MSG_SIZ];
9686     char *p;
9687
9688     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9689         p = def;
9690         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9691         *p++ = '-';
9692         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9693         *p++ = '.';
9694         strcpy(p, ext);
9695     } else {
9696         def[0] = NULLCHAR;
9697     }
9698     return def;
9699 }
9700
9701 /* Save the current game to the given file */
9702 int
9703 SaveGameToFile(filename, append)
9704      char *filename;
9705      int append;
9706 {
9707     FILE *f;
9708     char buf[MSG_SIZ];
9709
9710     if (strcmp(filename, "-") == 0) {
9711         return SaveGame(stdout, 0, NULL);
9712     } else {
9713         f = fopen(filename, append ? "a" : "w");
9714         if (f == NULL) {
9715             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9716             DisplayError(buf, errno);
9717             return FALSE;
9718         } else {
9719             return SaveGame(f, 0, NULL);
9720         }
9721     }
9722 }
9723
9724 char *
9725 SavePart(str)
9726      char *str;
9727 {
9728     static char buf[MSG_SIZ];
9729     char *p;
9730     
9731     p = strchr(str, ' ');
9732     if (p == NULL) return str;
9733     strncpy(buf, str, p - str);
9734     buf[p - str] = NULLCHAR;
9735     return buf;
9736 }
9737
9738 #define PGN_MAX_LINE 75
9739
9740 #define PGN_SIDE_WHITE  0
9741 #define PGN_SIDE_BLACK  1
9742
9743 /* [AS] */
9744 static int FindFirstMoveOutOfBook( int side )
9745 {
9746     int result = -1;
9747
9748     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9749         int index = backwardMostMove;
9750         int has_book_hit = 0;
9751
9752         if( (index % 2) != side ) {
9753             index++;
9754         }
9755
9756         while( index < forwardMostMove ) {
9757             /* Check to see if engine is in book */
9758             int depth = pvInfoList[index].depth;
9759             int score = pvInfoList[index].score;
9760             int in_book = 0;
9761
9762             if( depth <= 2 ) {
9763                 in_book = 1;
9764             }
9765             else if( score == 0 && depth == 63 ) {
9766                 in_book = 1; /* Zappa */
9767             }
9768             else if( score == 2 && depth == 99 ) {
9769                 in_book = 1; /* Abrok */
9770             }
9771
9772             has_book_hit += in_book;
9773
9774             if( ! in_book ) {
9775                 result = index;
9776
9777                 break;
9778             }
9779
9780             index += 2;
9781         }
9782     }
9783
9784     return result;
9785 }
9786
9787 /* [AS] */
9788 void GetOutOfBookInfo( char * buf )
9789 {
9790     int oob[2];
9791     int i;
9792     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9793
9794     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9795     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9796
9797     *buf = '\0';
9798
9799     if( oob[0] >= 0 || oob[1] >= 0 ) {
9800         for( i=0; i<2; i++ ) {
9801             int idx = oob[i];
9802
9803             if( idx >= 0 ) {
9804                 if( i > 0 && oob[0] >= 0 ) {
9805                     strcat( buf, "   " );
9806                 }
9807
9808                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9809                 sprintf( buf+strlen(buf), "%s%.2f", 
9810                     pvInfoList[idx].score >= 0 ? "+" : "",
9811                     pvInfoList[idx].score / 100.0 );
9812             }
9813         }
9814     }
9815 }
9816
9817 /* Save game in PGN style and close the file */
9818 int
9819 SaveGamePGN(f)
9820      FILE *f;
9821 {
9822     int i, offset, linelen, newblock;
9823     time_t tm;
9824 //    char *movetext;
9825     char numtext[32];
9826     int movelen, numlen, blank;
9827     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9828
9829     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9830     
9831     tm = time((time_t *) NULL);
9832     
9833     PrintPGNTags(f, &gameInfo);
9834     
9835     if (backwardMostMove > 0 || startedFromSetupPosition) {
9836         char *fen = PositionToFEN(backwardMostMove, NULL);
9837         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9838         fprintf(f, "\n{--------------\n");
9839         PrintPosition(f, backwardMostMove);
9840         fprintf(f, "--------------}\n");
9841         free(fen);
9842     }
9843     else {
9844         /* [AS] Out of book annotation */
9845         if( appData.saveOutOfBookInfo ) {
9846             char buf[64];
9847
9848             GetOutOfBookInfo( buf );
9849
9850             if( buf[0] != '\0' ) {
9851                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9852             }
9853         }
9854
9855         fprintf(f, "\n");
9856     }
9857
9858     i = backwardMostMove;
9859     linelen = 0;
9860     newblock = TRUE;
9861
9862     while (i < forwardMostMove) {
9863         /* Print comments preceding this move */
9864         if (commentList[i] != NULL) {
9865             if (linelen > 0) fprintf(f, "\n");
9866             fprintf(f, "%s\n", commentList[i]);
9867             linelen = 0;
9868             newblock = TRUE;
9869         }
9870
9871         /* Format move number */
9872         if ((i % 2) == 0) {
9873             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9874         } else {
9875             if (newblock) {
9876                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9877             } else {
9878                 numtext[0] = NULLCHAR;
9879             }
9880         }
9881         numlen = strlen(numtext);
9882         newblock = FALSE;
9883
9884         /* Print move number */
9885         blank = linelen > 0 && numlen > 0;
9886         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9887             fprintf(f, "\n");
9888             linelen = 0;
9889             blank = 0;
9890         }
9891         if (blank) {
9892             fprintf(f, " ");
9893             linelen++;
9894         }
9895         fprintf(f, "%s", numtext);
9896         linelen += numlen;
9897
9898         /* Get move */
9899         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9900         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9901
9902         /* Print move */
9903         blank = linelen > 0 && movelen > 0;
9904         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9905             fprintf(f, "\n");
9906             linelen = 0;
9907             blank = 0;
9908         }
9909         if (blank) {
9910             fprintf(f, " ");
9911             linelen++;
9912         }
9913         fprintf(f, "%s", move_buffer);
9914         linelen += movelen;
9915
9916         /* [AS] Add PV info if present */
9917         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9918             /* [HGM] add time */
9919             char buf[MSG_SIZ]; int seconds = 0;
9920
9921             if(i >= backwardMostMove) {
9922                 if(WhiteOnMove(i))
9923                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9924                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9925                 else
9926                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9927                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9928             }
9929             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9930
9931             if( seconds <= 0) buf[0] = 0; else
9932             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9933                 seconds = (seconds + 4)/10; // round to full seconds
9934                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9935                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9936             }
9937
9938             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9939                 pvInfoList[i].score >= 0 ? "+" : "",
9940                 pvInfoList[i].score / 100.0,
9941                 pvInfoList[i].depth,
9942                 buf );
9943
9944             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9945
9946             /* Print score/depth */
9947             blank = linelen > 0 && movelen > 0;
9948             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9949                 fprintf(f, "\n");
9950                 linelen = 0;
9951                 blank = 0;
9952             }
9953             if (blank) {
9954                 fprintf(f, " ");
9955                 linelen++;
9956             }
9957             fprintf(f, "%s", move_buffer);
9958             linelen += movelen;
9959         }
9960
9961         i++;
9962     }
9963     
9964     /* Start a new line */
9965     if (linelen > 0) fprintf(f, "\n");
9966
9967     /* Print comments after last move */
9968     if (commentList[i] != NULL) {
9969         fprintf(f, "%s\n", commentList[i]);
9970     }
9971
9972     /* Print result */
9973     if (gameInfo.resultDetails != NULL &&
9974         gameInfo.resultDetails[0] != NULLCHAR) {
9975         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9976                 PGNResult(gameInfo.result));
9977     } else {
9978         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9979     }
9980
9981     fclose(f);
9982     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9983     return TRUE;
9984 }
9985
9986 /* Save game in old style and close the file */
9987 int
9988 SaveGameOldStyle(f)
9989      FILE *f;
9990 {
9991     int i, offset;
9992     time_t tm;
9993     
9994     tm = time((time_t *) NULL);
9995     
9996     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9997     PrintOpponents(f);
9998     
9999     if (backwardMostMove > 0 || startedFromSetupPosition) {
10000         fprintf(f, "\n[--------------\n");
10001         PrintPosition(f, backwardMostMove);
10002         fprintf(f, "--------------]\n");
10003     } else {
10004         fprintf(f, "\n");
10005     }
10006
10007     i = backwardMostMove;
10008     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10009
10010     while (i < forwardMostMove) {
10011         if (commentList[i] != NULL) {
10012             fprintf(f, "[%s]\n", commentList[i]);
10013         }
10014
10015         if ((i % 2) == 1) {
10016             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10017             i++;
10018         } else {
10019             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10020             i++;
10021             if (commentList[i] != NULL) {
10022                 fprintf(f, "\n");
10023                 continue;
10024             }
10025             if (i >= forwardMostMove) {
10026                 fprintf(f, "\n");
10027                 break;
10028             }
10029             fprintf(f, "%s\n", parseList[i]);
10030             i++;
10031         }
10032     }
10033     
10034     if (commentList[i] != NULL) {
10035         fprintf(f, "[%s]\n", commentList[i]);
10036     }
10037
10038     /* This isn't really the old style, but it's close enough */
10039     if (gameInfo.resultDetails != NULL &&
10040         gameInfo.resultDetails[0] != NULLCHAR) {
10041         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10042                 gameInfo.resultDetails);
10043     } else {
10044         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10045     }
10046
10047     fclose(f);
10048     return TRUE;
10049 }
10050
10051 /* Save the current game to open file f and close the file */
10052 int
10053 SaveGame(f, dummy, dummy2)
10054      FILE *f;
10055      int dummy;
10056      char *dummy2;
10057 {
10058     if (gameMode == EditPosition) EditPositionDone(TRUE);
10059     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10060     if (appData.oldSaveStyle)
10061       return SaveGameOldStyle(f);
10062     else
10063       return SaveGamePGN(f);
10064 }
10065
10066 /* Save the current position to the given file */
10067 int
10068 SavePositionToFile(filename)
10069      char *filename;
10070 {
10071     FILE *f;
10072     char buf[MSG_SIZ];
10073
10074     if (strcmp(filename, "-") == 0) {
10075         return SavePosition(stdout, 0, NULL);
10076     } else {
10077         f = fopen(filename, "a");
10078         if (f == NULL) {
10079             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10080             DisplayError(buf, errno);
10081             return FALSE;
10082         } else {
10083             SavePosition(f, 0, NULL);
10084             return TRUE;
10085         }
10086     }
10087 }
10088
10089 /* Save the current position to the given open file and close the file */
10090 int
10091 SavePosition(f, dummy, dummy2)
10092      FILE *f;
10093      int dummy;
10094      char *dummy2;
10095 {
10096     time_t tm;
10097     char *fen;
10098     
10099     if (appData.oldSaveStyle) {
10100         tm = time((time_t *) NULL);
10101     
10102         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10103         PrintOpponents(f);
10104         fprintf(f, "[--------------\n");
10105         PrintPosition(f, currentMove);
10106         fprintf(f, "--------------]\n");
10107     } else {
10108         fen = PositionToFEN(currentMove, NULL);
10109         fprintf(f, "%s\n", fen);
10110         free(fen);
10111     }
10112     fclose(f);
10113     return TRUE;
10114 }
10115
10116 void
10117 ReloadCmailMsgEvent(unregister)
10118      int unregister;
10119 {
10120 #if !WIN32
10121     static char *inFilename = NULL;
10122     static char *outFilename;
10123     int i;
10124     struct stat inbuf, outbuf;
10125     int status;
10126     
10127     /* Any registered moves are unregistered if unregister is set, */
10128     /* i.e. invoked by the signal handler */
10129     if (unregister) {
10130         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10131             cmailMoveRegistered[i] = FALSE;
10132             if (cmailCommentList[i] != NULL) {
10133                 free(cmailCommentList[i]);
10134                 cmailCommentList[i] = NULL;
10135             }
10136         }
10137         nCmailMovesRegistered = 0;
10138     }
10139
10140     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10141         cmailResult[i] = CMAIL_NOT_RESULT;
10142     }
10143     nCmailResults = 0;
10144
10145     if (inFilename == NULL) {
10146         /* Because the filenames are static they only get malloced once  */
10147         /* and they never get freed                                      */
10148         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10149         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10150
10151         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10152         sprintf(outFilename, "%s.out", appData.cmailGameName);
10153     }
10154     
10155     status = stat(outFilename, &outbuf);
10156     if (status < 0) {
10157         cmailMailedMove = FALSE;
10158     } else {
10159         status = stat(inFilename, &inbuf);
10160         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10161     }
10162     
10163     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10164        counts the games, notes how each one terminated, etc.
10165        
10166        It would be nice to remove this kludge and instead gather all
10167        the information while building the game list.  (And to keep it
10168        in the game list nodes instead of having a bunch of fixed-size
10169        parallel arrays.)  Note this will require getting each game's
10170        termination from the PGN tags, as the game list builder does
10171        not process the game moves.  --mann
10172        */
10173     cmailMsgLoaded = TRUE;
10174     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10175     
10176     /* Load first game in the file or popup game menu */
10177     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10178
10179 #endif /* !WIN32 */
10180     return;
10181 }
10182
10183 int
10184 RegisterMove()
10185 {
10186     FILE *f;
10187     char string[MSG_SIZ];
10188
10189     if (   cmailMailedMove
10190         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10191         return TRUE;            /* Allow free viewing  */
10192     }
10193
10194     /* Unregister move to ensure that we don't leave RegisterMove        */
10195     /* with the move registered when the conditions for registering no   */
10196     /* longer hold                                                       */
10197     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10198         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10199         nCmailMovesRegistered --;
10200
10201         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10202           {
10203               free(cmailCommentList[lastLoadGameNumber - 1]);
10204               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10205           }
10206     }
10207
10208     if (cmailOldMove == -1) {
10209         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10210         return FALSE;
10211     }
10212
10213     if (currentMove > cmailOldMove + 1) {
10214         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10215         return FALSE;
10216     }
10217
10218     if (currentMove < cmailOldMove) {
10219         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10220         return FALSE;
10221     }
10222
10223     if (forwardMostMove > currentMove) {
10224         /* Silently truncate extra moves */
10225         TruncateGame();
10226     }
10227
10228     if (   (currentMove == cmailOldMove + 1)
10229         || (   (currentMove == cmailOldMove)
10230             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10231                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10232         if (gameInfo.result != GameUnfinished) {
10233             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10234         }
10235
10236         if (commentList[currentMove] != NULL) {
10237             cmailCommentList[lastLoadGameNumber - 1]
10238               = StrSave(commentList[currentMove]);
10239         }
10240         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10241
10242         if (appData.debugMode)
10243           fprintf(debugFP, "Saving %s for game %d\n",
10244                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10245
10246         sprintf(string,
10247                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10248         
10249         f = fopen(string, "w");
10250         if (appData.oldSaveStyle) {
10251             SaveGameOldStyle(f); /* also closes the file */
10252             
10253             sprintf(string, "%s.pos.out", appData.cmailGameName);
10254             f = fopen(string, "w");
10255             SavePosition(f, 0, NULL); /* also closes the file */
10256         } else {
10257             fprintf(f, "{--------------\n");
10258             PrintPosition(f, currentMove);
10259             fprintf(f, "--------------}\n\n");
10260             
10261             SaveGame(f, 0, NULL); /* also closes the file*/
10262         }
10263         
10264         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10265         nCmailMovesRegistered ++;
10266     } else if (nCmailGames == 1) {
10267         DisplayError(_("You have not made a move yet"), 0);
10268         return FALSE;
10269     }
10270
10271     return TRUE;
10272 }
10273
10274 void
10275 MailMoveEvent()
10276 {
10277 #if !WIN32
10278     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10279     FILE *commandOutput;
10280     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10281     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10282     int nBuffers;
10283     int i;
10284     int archived;
10285     char *arcDir;
10286
10287     if (! cmailMsgLoaded) {
10288         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10289         return;
10290     }
10291
10292     if (nCmailGames == nCmailResults) {
10293         DisplayError(_("No unfinished games"), 0);
10294         return;
10295     }
10296
10297 #if CMAIL_PROHIBIT_REMAIL
10298     if (cmailMailedMove) {
10299         sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
10300         DisplayError(msg, 0);
10301         return;
10302     }
10303 #endif
10304
10305     if (! (cmailMailedMove || RegisterMove())) return;
10306     
10307     if (   cmailMailedMove
10308         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10309         sprintf(string, partCommandString,
10310                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10311         commandOutput = popen(string, "r");
10312
10313         if (commandOutput == NULL) {
10314             DisplayError(_("Failed to invoke cmail"), 0);
10315         } else {
10316             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10317                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10318             }
10319             if (nBuffers > 1) {
10320                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10321                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10322                 nBytes = MSG_SIZ - 1;
10323             } else {
10324                 (void) memcpy(msg, buffer, nBytes);
10325             }
10326             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10327
10328             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10329                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10330
10331                 archived = TRUE;
10332                 for (i = 0; i < nCmailGames; i ++) {
10333                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10334                         archived = FALSE;
10335                     }
10336                 }
10337                 if (   archived
10338                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10339                         != NULL)) {
10340                     sprintf(buffer, "%s/%s.%s.archive",
10341                             arcDir,
10342                             appData.cmailGameName,
10343                             gameInfo.date);
10344                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10345                     cmailMsgLoaded = FALSE;
10346                 }
10347             }
10348
10349             DisplayInformation(msg);
10350             pclose(commandOutput);
10351         }
10352     } else {
10353         if ((*cmailMsg) != '\0') {
10354             DisplayInformation(cmailMsg);
10355         }
10356     }
10357
10358     return;
10359 #endif /* !WIN32 */
10360 }
10361
10362 char *
10363 CmailMsg()
10364 {
10365 #if WIN32
10366     return NULL;
10367 #else
10368     int  prependComma = 0;
10369     char number[5];
10370     char string[MSG_SIZ];       /* Space for game-list */
10371     int  i;
10372     
10373     if (!cmailMsgLoaded) return "";
10374
10375     if (cmailMailedMove) {
10376         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10377     } else {
10378         /* Create a list of games left */
10379         sprintf(string, "[");
10380         for (i = 0; i < nCmailGames; i ++) {
10381             if (! (   cmailMoveRegistered[i]
10382                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10383                 if (prependComma) {
10384                     sprintf(number, ",%d", i + 1);
10385                 } else {
10386                     sprintf(number, "%d", i + 1);
10387                     prependComma = 1;
10388                 }
10389                 
10390                 strcat(string, number);
10391             }
10392         }
10393         strcat(string, "]");
10394
10395         if (nCmailMovesRegistered + nCmailResults == 0) {
10396             switch (nCmailGames) {
10397               case 1:
10398                 sprintf(cmailMsg,
10399                         _("Still need to make move for game\n"));
10400                 break;
10401                 
10402               case 2:
10403                 sprintf(cmailMsg,
10404                         _("Still need to make moves for both games\n"));
10405                 break;
10406                 
10407               default:
10408                 sprintf(cmailMsg,
10409                         _("Still need to make moves for all %d games\n"),
10410                         nCmailGames);
10411                 break;
10412             }
10413         } else {
10414             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10415               case 1:
10416                 sprintf(cmailMsg,
10417                         _("Still need to make a move for game %s\n"),
10418                         string);
10419                 break;
10420                 
10421               case 0:
10422                 if (nCmailResults == nCmailGames) {
10423                     sprintf(cmailMsg, _("No unfinished games\n"));
10424                 } else {
10425                     sprintf(cmailMsg, _("Ready to send mail\n"));
10426                 }
10427                 break;
10428                 
10429               default:
10430                 sprintf(cmailMsg,
10431                         _("Still need to make moves for games %s\n"),
10432                         string);
10433             }
10434         }
10435     }
10436     return cmailMsg;
10437 #endif /* WIN32 */
10438 }
10439
10440 void
10441 ResetGameEvent()
10442 {
10443     if (gameMode == Training)
10444       SetTrainingModeOff();
10445
10446     Reset(TRUE, TRUE);
10447     cmailMsgLoaded = FALSE;
10448     if (appData.icsActive) {
10449       SendToICS(ics_prefix);
10450       SendToICS("refresh\n");
10451     }
10452 }
10453
10454 void
10455 ExitEvent(status)
10456      int status;
10457 {
10458     exiting++;
10459     if (exiting > 2) {
10460       /* Give up on clean exit */
10461       exit(status);
10462     }
10463     if (exiting > 1) {
10464       /* Keep trying for clean exit */
10465       return;
10466     }
10467
10468     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10469
10470     if (telnetISR != NULL) {
10471       RemoveInputSource(telnetISR);
10472     }
10473     if (icsPR != NoProc) {
10474       DestroyChildProcess(icsPR, TRUE);
10475     }
10476
10477     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10478     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10479
10480     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10481     /* make sure this other one finishes before killing it!                  */
10482     if(endingGame) { int count = 0;
10483         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10484         while(endingGame && count++ < 10) DoSleep(1);
10485         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10486     }
10487
10488     /* Kill off chess programs */
10489     if (first.pr != NoProc) {
10490         ExitAnalyzeMode();
10491         
10492         DoSleep( appData.delayBeforeQuit );
10493         SendToProgram("quit\n", &first);
10494         DoSleep( appData.delayAfterQuit );
10495         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10496     }
10497     if (second.pr != NoProc) {
10498         DoSleep( appData.delayBeforeQuit );
10499         SendToProgram("quit\n", &second);
10500         DoSleep( appData.delayAfterQuit );
10501         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10502     }
10503     if (first.isr != NULL) {
10504         RemoveInputSource(first.isr);
10505     }
10506     if (second.isr != NULL) {
10507         RemoveInputSource(second.isr);
10508     }
10509
10510     ShutDownFrontEnd();
10511     exit(status);
10512 }
10513
10514 void
10515 PauseEvent()
10516 {
10517     if (appData.debugMode)
10518         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10519     if (pausing) {
10520         pausing = FALSE;
10521         ModeHighlight();
10522         if (gameMode == MachinePlaysWhite ||
10523             gameMode == MachinePlaysBlack) {
10524             StartClocks();
10525         } else {
10526             DisplayBothClocks();
10527         }
10528         if (gameMode == PlayFromGameFile) {
10529             if (appData.timeDelay >= 0) 
10530                 AutoPlayGameLoop();
10531         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10532             Reset(FALSE, TRUE);
10533             SendToICS(ics_prefix);
10534             SendToICS("refresh\n");
10535         } else if (currentMove < forwardMostMove) {
10536             ForwardInner(forwardMostMove);
10537         }
10538         pauseExamInvalid = FALSE;
10539     } else {
10540         switch (gameMode) {
10541           default:
10542             return;
10543           case IcsExamining:
10544             pauseExamForwardMostMove = forwardMostMove;
10545             pauseExamInvalid = FALSE;
10546             /* fall through */
10547           case IcsObserving:
10548           case IcsPlayingWhite:
10549           case IcsPlayingBlack:
10550             pausing = TRUE;
10551             ModeHighlight();
10552             return;
10553           case PlayFromGameFile:
10554             (void) StopLoadGameTimer();
10555             pausing = TRUE;
10556             ModeHighlight();
10557             break;
10558           case BeginningOfGame:
10559             if (appData.icsActive) return;
10560             /* else fall through */
10561           case MachinePlaysWhite:
10562           case MachinePlaysBlack:
10563           case TwoMachinesPlay:
10564             if (forwardMostMove == 0)
10565               return;           /* don't pause if no one has moved */
10566             if ((gameMode == MachinePlaysWhite &&
10567                  !WhiteOnMove(forwardMostMove)) ||
10568                 (gameMode == MachinePlaysBlack &&
10569                  WhiteOnMove(forwardMostMove))) {
10570                 StopClocks();
10571             }
10572             pausing = TRUE;
10573             ModeHighlight();
10574             break;
10575         }
10576     }
10577 }
10578
10579 void
10580 EditCommentEvent()
10581 {
10582     char title[MSG_SIZ];
10583
10584     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10585         strcpy(title, _("Edit comment"));
10586     } else {
10587         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10588                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10589                 parseList[currentMove - 1]);
10590     }
10591
10592     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10593 }
10594
10595
10596 void
10597 EditTagsEvent()
10598 {
10599     char *tags = PGNTags(&gameInfo);
10600     EditTagsPopUp(tags);
10601     free(tags);
10602 }
10603
10604 void
10605 AnalyzeModeEvent()
10606 {
10607     if (appData.noChessProgram || gameMode == AnalyzeMode)
10608       return;
10609
10610     if (gameMode != AnalyzeFile) {
10611         if (!appData.icsEngineAnalyze) {
10612                EditGameEvent();
10613                if (gameMode != EditGame) return;
10614         }
10615         ResurrectChessProgram();
10616         SendToProgram("analyze\n", &first);
10617         first.analyzing = TRUE;
10618         /*first.maybeThinking = TRUE;*/
10619         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10620         EngineOutputPopUp();
10621     }
10622     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10623     pausing = FALSE;
10624     ModeHighlight();
10625     SetGameInfo();
10626
10627     StartAnalysisClock();
10628     GetTimeMark(&lastNodeCountTime);
10629     lastNodeCount = 0;
10630 }
10631
10632 void
10633 AnalyzeFileEvent()
10634 {
10635     if (appData.noChessProgram || gameMode == AnalyzeFile)
10636       return;
10637
10638     if (gameMode != AnalyzeMode) {
10639         EditGameEvent();
10640         if (gameMode != EditGame) return;
10641         ResurrectChessProgram();
10642         SendToProgram("analyze\n", &first);
10643         first.analyzing = TRUE;
10644         /*first.maybeThinking = TRUE;*/
10645         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10646         EngineOutputPopUp();
10647     }
10648     gameMode = AnalyzeFile;
10649     pausing = FALSE;
10650     ModeHighlight();
10651     SetGameInfo();
10652
10653     StartAnalysisClock();
10654     GetTimeMark(&lastNodeCountTime);
10655     lastNodeCount = 0;
10656 }
10657
10658 void
10659 MachineWhiteEvent()
10660 {
10661     char buf[MSG_SIZ];
10662     char *bookHit = NULL;
10663
10664     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10665       return;
10666
10667
10668     if (gameMode == PlayFromGameFile || 
10669         gameMode == TwoMachinesPlay  || 
10670         gameMode == Training         || 
10671         gameMode == AnalyzeMode      || 
10672         gameMode == EndOfGame)
10673         EditGameEvent();
10674
10675     if (gameMode == EditPosition) 
10676         EditPositionDone(TRUE);
10677
10678     if (!WhiteOnMove(currentMove)) {
10679         DisplayError(_("It is not White's turn"), 0);
10680         return;
10681     }
10682   
10683     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10684       ExitAnalyzeMode();
10685
10686     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10687         gameMode == AnalyzeFile)
10688         TruncateGame();
10689
10690     ResurrectChessProgram();    /* in case it isn't running */
10691     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10692         gameMode = MachinePlaysWhite;
10693         ResetClocks();
10694     } else
10695     gameMode = MachinePlaysWhite;
10696     pausing = FALSE;
10697     ModeHighlight();
10698     SetGameInfo();
10699     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10700     DisplayTitle(buf);
10701     if (first.sendName) {
10702       sprintf(buf, "name %s\n", gameInfo.black);
10703       SendToProgram(buf, &first);
10704     }
10705     if (first.sendTime) {
10706       if (first.useColors) {
10707         SendToProgram("black\n", &first); /*gnu kludge*/
10708       }
10709       SendTimeRemaining(&first, TRUE);
10710     }
10711     if (first.useColors) {
10712       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10713     }
10714     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10715     SetMachineThinkingEnables();
10716     first.maybeThinking = TRUE;
10717     StartClocks();
10718     firstMove = FALSE;
10719
10720     if (appData.autoFlipView && !flipView) {
10721       flipView = !flipView;
10722       DrawPosition(FALSE, NULL);
10723       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10724     }
10725
10726     if(bookHit) { // [HGM] book: simulate book reply
10727         static char bookMove[MSG_SIZ]; // a bit generous?
10728
10729         programStats.nodes = programStats.depth = programStats.time = 
10730         programStats.score = programStats.got_only_move = 0;
10731         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10732
10733         strcpy(bookMove, "move ");
10734         strcat(bookMove, bookHit);
10735         HandleMachineMove(bookMove, &first);
10736     }
10737 }
10738
10739 void
10740 MachineBlackEvent()
10741 {
10742     char buf[MSG_SIZ];
10743    char *bookHit = NULL;
10744
10745     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10746         return;
10747
10748
10749     if (gameMode == PlayFromGameFile || 
10750         gameMode == TwoMachinesPlay  || 
10751         gameMode == Training         || 
10752         gameMode == AnalyzeMode      || 
10753         gameMode == EndOfGame)
10754         EditGameEvent();
10755
10756     if (gameMode == EditPosition) 
10757         EditPositionDone(TRUE);
10758
10759     if (WhiteOnMove(currentMove)) {
10760         DisplayError(_("It is not Black's turn"), 0);
10761         return;
10762     }
10763     
10764     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10765       ExitAnalyzeMode();
10766
10767     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10768         gameMode == AnalyzeFile)
10769         TruncateGame();
10770
10771     ResurrectChessProgram();    /* in case it isn't running */
10772     gameMode = MachinePlaysBlack;
10773     pausing = FALSE;
10774     ModeHighlight();
10775     SetGameInfo();
10776     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10777     DisplayTitle(buf);
10778     if (first.sendName) {
10779       sprintf(buf, "name %s\n", gameInfo.white);
10780       SendToProgram(buf, &first);
10781     }
10782     if (first.sendTime) {
10783       if (first.useColors) {
10784         SendToProgram("white\n", &first); /*gnu kludge*/
10785       }
10786       SendTimeRemaining(&first, FALSE);
10787     }
10788     if (first.useColors) {
10789       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10790     }
10791     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10792     SetMachineThinkingEnables();
10793     first.maybeThinking = TRUE;
10794     StartClocks();
10795
10796     if (appData.autoFlipView && flipView) {
10797       flipView = !flipView;
10798       DrawPosition(FALSE, NULL);
10799       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10800     }
10801     if(bookHit) { // [HGM] book: simulate book reply
10802         static char bookMove[MSG_SIZ]; // a bit generous?
10803
10804         programStats.nodes = programStats.depth = programStats.time = 
10805         programStats.score = programStats.got_only_move = 0;
10806         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10807
10808         strcpy(bookMove, "move ");
10809         strcat(bookMove, bookHit);
10810         HandleMachineMove(bookMove, &first);
10811     }
10812 }
10813
10814
10815 void
10816 DisplayTwoMachinesTitle()
10817 {
10818     char buf[MSG_SIZ];
10819     if (appData.matchGames > 0) {
10820         if (first.twoMachinesColor[0] == 'w') {
10821             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10822                     gameInfo.white, gameInfo.black,
10823                     first.matchWins, second.matchWins,
10824                     matchGame - 1 - (first.matchWins + second.matchWins));
10825         } else {
10826             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10827                     gameInfo.white, gameInfo.black,
10828                     second.matchWins, first.matchWins,
10829                     matchGame - 1 - (first.matchWins + second.matchWins));
10830         }
10831     } else {
10832         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10833     }
10834     DisplayTitle(buf);
10835 }
10836
10837 void
10838 TwoMachinesEvent P((void))
10839 {
10840     int i;
10841     char buf[MSG_SIZ];
10842     ChessProgramState *onmove;
10843     char *bookHit = NULL;
10844     
10845     if (appData.noChessProgram) return;
10846
10847     switch (gameMode) {
10848       case TwoMachinesPlay:
10849         return;
10850       case MachinePlaysWhite:
10851       case MachinePlaysBlack:
10852         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10853             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10854             return;
10855         }
10856         /* fall through */
10857       case BeginningOfGame:
10858       case PlayFromGameFile:
10859       case EndOfGame:
10860         EditGameEvent();
10861         if (gameMode != EditGame) return;
10862         break;
10863       case EditPosition:
10864         EditPositionDone(TRUE);
10865         break;
10866       case AnalyzeMode:
10867       case AnalyzeFile:
10868         ExitAnalyzeMode();
10869         break;
10870       case EditGame:
10871       default:
10872         break;
10873     }
10874
10875 //    forwardMostMove = currentMove;
10876     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10877     ResurrectChessProgram();    /* in case first program isn't running */
10878
10879     if (second.pr == NULL) {
10880         StartChessProgram(&second);
10881         if (second.protocolVersion == 1) {
10882           TwoMachinesEventIfReady();
10883         } else {
10884           /* kludge: allow timeout for initial "feature" command */
10885           FreezeUI();
10886           DisplayMessage("", _("Starting second chess program"));
10887           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10888         }
10889         return;
10890     }
10891     DisplayMessage("", "");
10892     InitChessProgram(&second, FALSE);
10893     SendToProgram("force\n", &second);
10894     if (startedFromSetupPosition) {
10895         SendBoard(&second, backwardMostMove);
10896     if (appData.debugMode) {
10897         fprintf(debugFP, "Two Machines\n");
10898     }
10899     }
10900     for (i = backwardMostMove; i < forwardMostMove; i++) {
10901         SendMoveToProgram(i, &second);
10902     }
10903
10904     gameMode = TwoMachinesPlay;
10905     pausing = FALSE;
10906     ModeHighlight();
10907     SetGameInfo();
10908     DisplayTwoMachinesTitle();
10909     firstMove = TRUE;
10910     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10911         onmove = &first;
10912     } else {
10913         onmove = &second;
10914     }
10915
10916     SendToProgram(first.computerString, &first);
10917     if (first.sendName) {
10918       sprintf(buf, "name %s\n", second.tidy);
10919       SendToProgram(buf, &first);
10920     }
10921     SendToProgram(second.computerString, &second);
10922     if (second.sendName) {
10923       sprintf(buf, "name %s\n", first.tidy);
10924       SendToProgram(buf, &second);
10925     }
10926
10927     ResetClocks();
10928     if (!first.sendTime || !second.sendTime) {
10929         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10930         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10931     }
10932     if (onmove->sendTime) {
10933       if (onmove->useColors) {
10934         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10935       }
10936       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10937     }
10938     if (onmove->useColors) {
10939       SendToProgram(onmove->twoMachinesColor, onmove);
10940     }
10941     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10942 //    SendToProgram("go\n", onmove);
10943     onmove->maybeThinking = TRUE;
10944     SetMachineThinkingEnables();
10945
10946     StartClocks();
10947
10948     if(bookHit) { // [HGM] book: simulate book reply
10949         static char bookMove[MSG_SIZ]; // a bit generous?
10950
10951         programStats.nodes = programStats.depth = programStats.time = 
10952         programStats.score = programStats.got_only_move = 0;
10953         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10954
10955         strcpy(bookMove, "move ");
10956         strcat(bookMove, bookHit);
10957         savedMessage = bookMove; // args for deferred call
10958         savedState = onmove;
10959         ScheduleDelayedEvent(DeferredBookMove, 1);
10960     }
10961 }
10962
10963 void
10964 TrainingEvent()
10965 {
10966     if (gameMode == Training) {
10967       SetTrainingModeOff();
10968       gameMode = PlayFromGameFile;
10969       DisplayMessage("", _("Training mode off"));
10970     } else {
10971       gameMode = Training;
10972       animateTraining = appData.animate;
10973
10974       /* make sure we are not already at the end of the game */
10975       if (currentMove < forwardMostMove) {
10976         SetTrainingModeOn();
10977         DisplayMessage("", _("Training mode on"));
10978       } else {
10979         gameMode = PlayFromGameFile;
10980         DisplayError(_("Already at end of game"), 0);
10981       }
10982     }
10983     ModeHighlight();
10984 }
10985
10986 void
10987 IcsClientEvent()
10988 {
10989     if (!appData.icsActive) return;
10990     switch (gameMode) {
10991       case IcsPlayingWhite:
10992       case IcsPlayingBlack:
10993       case IcsObserving:
10994       case IcsIdle:
10995       case BeginningOfGame:
10996       case IcsExamining:
10997         return;
10998
10999       case EditGame:
11000         break;
11001
11002       case EditPosition:
11003         EditPositionDone(TRUE);
11004         break;
11005
11006       case AnalyzeMode:
11007       case AnalyzeFile:
11008         ExitAnalyzeMode();
11009         break;
11010         
11011       default:
11012         EditGameEvent();
11013         break;
11014     }
11015
11016     gameMode = IcsIdle;
11017     ModeHighlight();
11018     return;
11019 }
11020
11021
11022 void
11023 EditGameEvent()
11024 {
11025     int i;
11026
11027     switch (gameMode) {
11028       case Training:
11029         SetTrainingModeOff();
11030         break;
11031       case MachinePlaysWhite:
11032       case MachinePlaysBlack:
11033       case BeginningOfGame:
11034         SendToProgram("force\n", &first);
11035         SetUserThinkingEnables();
11036         break;
11037       case PlayFromGameFile:
11038         (void) StopLoadGameTimer();
11039         if (gameFileFP != NULL) {
11040             gameFileFP = NULL;
11041         }
11042         break;
11043       case EditPosition:
11044         EditPositionDone(TRUE);
11045         break;
11046       case AnalyzeMode:
11047       case AnalyzeFile:
11048         ExitAnalyzeMode();
11049         SendToProgram("force\n", &first);
11050         break;
11051       case TwoMachinesPlay:
11052         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11053         ResurrectChessProgram();
11054         SetUserThinkingEnables();
11055         break;
11056       case EndOfGame:
11057         ResurrectChessProgram();
11058         break;
11059       case IcsPlayingBlack:
11060       case IcsPlayingWhite:
11061         DisplayError(_("Warning: You are still playing a game"), 0);
11062         break;
11063       case IcsObserving:
11064         DisplayError(_("Warning: You are still observing a game"), 0);
11065         break;
11066       case IcsExamining:
11067         DisplayError(_("Warning: You are still examining a game"), 0);
11068         break;
11069       case IcsIdle:
11070         break;
11071       case EditGame:
11072       default:
11073         return;
11074     }
11075     
11076     pausing = FALSE;
11077     StopClocks();
11078     first.offeredDraw = second.offeredDraw = 0;
11079
11080     if (gameMode == PlayFromGameFile) {
11081         whiteTimeRemaining = timeRemaining[0][currentMove];
11082         blackTimeRemaining = timeRemaining[1][currentMove];
11083         DisplayTitle("");
11084     }
11085
11086     if (gameMode == MachinePlaysWhite ||
11087         gameMode == MachinePlaysBlack ||
11088         gameMode == TwoMachinesPlay ||
11089         gameMode == EndOfGame) {
11090         i = forwardMostMove;
11091         while (i > currentMove) {
11092             SendToProgram("undo\n", &first);
11093             i--;
11094         }
11095         whiteTimeRemaining = timeRemaining[0][currentMove];
11096         blackTimeRemaining = timeRemaining[1][currentMove];
11097         DisplayBothClocks();
11098         if (whiteFlag || blackFlag) {
11099             whiteFlag = blackFlag = 0;
11100         }
11101         DisplayTitle("");
11102     }           
11103     
11104     gameMode = EditGame;
11105     ModeHighlight();
11106     SetGameInfo();
11107 }
11108
11109
11110 void
11111 EditPositionEvent()
11112 {
11113     if (gameMode == EditPosition) {
11114         EditGameEvent();
11115         return;
11116     }
11117     
11118     EditGameEvent();
11119     if (gameMode != EditGame) return;
11120     
11121     gameMode = EditPosition;
11122     ModeHighlight();
11123     SetGameInfo();
11124     if (currentMove > 0)
11125       CopyBoard(boards[0], boards[currentMove]);
11126     
11127     blackPlaysFirst = !WhiteOnMove(currentMove);
11128     ResetClocks();
11129     currentMove = forwardMostMove = backwardMostMove = 0;
11130     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11131     DisplayMove(-1);
11132 }
11133
11134 void
11135 ExitAnalyzeMode()
11136 {
11137     /* [DM] icsEngineAnalyze - possible call from other functions */
11138     if (appData.icsEngineAnalyze) {
11139         appData.icsEngineAnalyze = FALSE;
11140
11141         DisplayMessage("",_("Close ICS engine analyze..."));
11142     }
11143     if (first.analysisSupport && first.analyzing) {
11144       SendToProgram("exit\n", &first);
11145       first.analyzing = FALSE;
11146     }
11147     thinkOutput[0] = NULLCHAR;
11148 }
11149
11150 void
11151 EditPositionDone(Boolean fakeRights)
11152 {
11153     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11154
11155     startedFromSetupPosition = TRUE;
11156     InitChessProgram(&first, FALSE);
11157     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11158       boards[0][EP_STATUS] = EP_NONE;
11159       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11160     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11161         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11162         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11163       } else boards[0][CASTLING][2] = NoRights;
11164     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11165         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11166         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11167       } else boards[0][CASTLING][5] = NoRights;
11168     }
11169     SendToProgram("force\n", &first);
11170     if (blackPlaysFirst) {
11171         strcpy(moveList[0], "");
11172         strcpy(parseList[0], "");
11173         currentMove = forwardMostMove = backwardMostMove = 1;
11174         CopyBoard(boards[1], boards[0]);
11175     } else {
11176         currentMove = forwardMostMove = backwardMostMove = 0;
11177     }
11178     SendBoard(&first, forwardMostMove);
11179     if (appData.debugMode) {
11180         fprintf(debugFP, "EditPosDone\n");
11181     }
11182     DisplayTitle("");
11183     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11184     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11185     gameMode = EditGame;
11186     ModeHighlight();
11187     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11188     ClearHighlights(); /* [AS] */
11189 }
11190
11191 /* Pause for `ms' milliseconds */
11192 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11193 void
11194 TimeDelay(ms)
11195      long ms;
11196 {
11197     TimeMark m1, m2;
11198
11199     GetTimeMark(&m1);
11200     do {
11201         GetTimeMark(&m2);
11202     } while (SubtractTimeMarks(&m2, &m1) < ms);
11203 }
11204
11205 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11206 void
11207 SendMultiLineToICS(buf)
11208      char *buf;
11209 {
11210     char temp[MSG_SIZ+1], *p;
11211     int len;
11212
11213     len = strlen(buf);
11214     if (len > MSG_SIZ)
11215       len = MSG_SIZ;
11216   
11217     strncpy(temp, buf, len);
11218     temp[len] = 0;
11219
11220     p = temp;
11221     while (*p) {
11222         if (*p == '\n' || *p == '\r')
11223           *p = ' ';
11224         ++p;
11225     }
11226
11227     strcat(temp, "\n");
11228     SendToICS(temp);
11229     SendToPlayer(temp, strlen(temp));
11230 }
11231
11232 void
11233 SetWhiteToPlayEvent()
11234 {
11235     if (gameMode == EditPosition) {
11236         blackPlaysFirst = FALSE;
11237         DisplayBothClocks();    /* works because currentMove is 0 */
11238     } else if (gameMode == IcsExamining) {
11239         SendToICS(ics_prefix);
11240         SendToICS("tomove white\n");
11241     }
11242 }
11243
11244 void
11245 SetBlackToPlayEvent()
11246 {
11247     if (gameMode == EditPosition) {
11248         blackPlaysFirst = TRUE;
11249         currentMove = 1;        /* kludge */
11250         DisplayBothClocks();
11251         currentMove = 0;
11252     } else if (gameMode == IcsExamining) {
11253         SendToICS(ics_prefix);
11254         SendToICS("tomove black\n");
11255     }
11256 }
11257
11258 void
11259 EditPositionMenuEvent(selection, x, y)
11260      ChessSquare selection;
11261      int x, y;
11262 {
11263     char buf[MSG_SIZ];
11264     ChessSquare piece = boards[0][y][x];
11265
11266     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11267
11268     switch (selection) {
11269       case ClearBoard:
11270         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11271             SendToICS(ics_prefix);
11272             SendToICS("bsetup clear\n");
11273         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11274             SendToICS(ics_prefix);
11275             SendToICS("clearboard\n");
11276         } else {
11277             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11278                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11279                 for (y = 0; y < BOARD_HEIGHT; y++) {
11280                     if (gameMode == IcsExamining) {
11281                         if (boards[currentMove][y][x] != EmptySquare) {
11282                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11283                                     AAA + x, ONE + y);
11284                             SendToICS(buf);
11285                         }
11286                     } else {
11287                         boards[0][y][x] = p;
11288                     }
11289                 }
11290             }
11291         }
11292         if (gameMode == EditPosition) {
11293             DrawPosition(FALSE, boards[0]);
11294         }
11295         break;
11296
11297       case WhitePlay:
11298         SetWhiteToPlayEvent();
11299         break;
11300
11301       case BlackPlay:
11302         SetBlackToPlayEvent();
11303         break;
11304
11305       case EmptySquare:
11306         if (gameMode == IcsExamining) {
11307             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11308             SendToICS(buf);
11309         } else {
11310             boards[0][y][x] = EmptySquare;
11311             DrawPosition(FALSE, boards[0]);
11312         }
11313         break;
11314
11315       case PromotePiece:
11316         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11317            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11318             selection = (ChessSquare) (PROMOTED piece);
11319         } else if(piece == EmptySquare) selection = WhiteSilver;
11320         else selection = (ChessSquare)((int)piece - 1);
11321         goto defaultlabel;
11322
11323       case DemotePiece:
11324         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11325            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11326             selection = (ChessSquare) (DEMOTED piece);
11327         } else if(piece == EmptySquare) selection = BlackSilver;
11328         else selection = (ChessSquare)((int)piece + 1);       
11329         goto defaultlabel;
11330
11331       case WhiteQueen:
11332       case BlackQueen:
11333         if(gameInfo.variant == VariantShatranj ||
11334            gameInfo.variant == VariantXiangqi  ||
11335            gameInfo.variant == VariantCourier    )
11336             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11337         goto defaultlabel;
11338
11339       case WhiteKing:
11340       case BlackKing:
11341         if(gameInfo.variant == VariantXiangqi)
11342             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11343         if(gameInfo.variant == VariantKnightmate)
11344             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11345       default:
11346         defaultlabel:
11347         if (gameMode == IcsExamining) {
11348             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11349                     PieceToChar(selection), AAA + x, ONE + y);
11350             SendToICS(buf);
11351         } else {
11352             boards[0][y][x] = selection;
11353             DrawPosition(FALSE, boards[0]);
11354         }
11355         break;
11356     }
11357 }
11358
11359
11360 void
11361 DropMenuEvent(selection, x, y)
11362      ChessSquare selection;
11363      int x, y;
11364 {
11365     ChessMove moveType;
11366
11367     switch (gameMode) {
11368       case IcsPlayingWhite:
11369       case MachinePlaysBlack:
11370         if (!WhiteOnMove(currentMove)) {
11371             DisplayMoveError(_("It is Black's turn"));
11372             return;
11373         }
11374         moveType = WhiteDrop;
11375         break;
11376       case IcsPlayingBlack:
11377       case MachinePlaysWhite:
11378         if (WhiteOnMove(currentMove)) {
11379             DisplayMoveError(_("It is White's turn"));
11380             return;
11381         }
11382         moveType = BlackDrop;
11383         break;
11384       case EditGame:
11385         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11386         break;
11387       default:
11388         return;
11389     }
11390
11391     if (moveType == BlackDrop && selection < BlackPawn) {
11392       selection = (ChessSquare) ((int) selection
11393                                  + (int) BlackPawn - (int) WhitePawn);
11394     }
11395     if (boards[currentMove][y][x] != EmptySquare) {
11396         DisplayMoveError(_("That square is occupied"));
11397         return;
11398     }
11399
11400     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11401 }
11402
11403 void
11404 AcceptEvent()
11405 {
11406     /* Accept a pending offer of any kind from opponent */
11407     
11408     if (appData.icsActive) {
11409         SendToICS(ics_prefix);
11410         SendToICS("accept\n");
11411     } else if (cmailMsgLoaded) {
11412         if (currentMove == cmailOldMove &&
11413             commentList[cmailOldMove] != NULL &&
11414             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11415                    "Black offers a draw" : "White offers a draw")) {
11416             TruncateGame();
11417             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11418             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11419         } else {
11420             DisplayError(_("There is no pending offer on this move"), 0);
11421             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11422         }
11423     } else {
11424         /* Not used for offers from chess program */
11425     }
11426 }
11427
11428 void
11429 DeclineEvent()
11430 {
11431     /* Decline a pending offer of any kind from opponent */
11432     
11433     if (appData.icsActive) {
11434         SendToICS(ics_prefix);
11435         SendToICS("decline\n");
11436     } else if (cmailMsgLoaded) {
11437         if (currentMove == cmailOldMove &&
11438             commentList[cmailOldMove] != NULL &&
11439             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11440                    "Black offers a draw" : "White offers a draw")) {
11441 #ifdef NOTDEF
11442             AppendComment(cmailOldMove, "Draw declined", TRUE);
11443             DisplayComment(cmailOldMove - 1, "Draw declined");
11444 #endif /*NOTDEF*/
11445         } else {
11446             DisplayError(_("There is no pending offer on this move"), 0);
11447         }
11448     } else {
11449         /* Not used for offers from chess program */
11450     }
11451 }
11452
11453 void
11454 RematchEvent()
11455 {
11456     /* Issue ICS rematch command */
11457     if (appData.icsActive) {
11458         SendToICS(ics_prefix);
11459         SendToICS("rematch\n");
11460     }
11461 }
11462
11463 void
11464 CallFlagEvent()
11465 {
11466     /* Call your opponent's flag (claim a win on time) */
11467     if (appData.icsActive) {
11468         SendToICS(ics_prefix);
11469         SendToICS("flag\n");
11470     } else {
11471         switch (gameMode) {
11472           default:
11473             return;
11474           case MachinePlaysWhite:
11475             if (whiteFlag) {
11476                 if (blackFlag)
11477                   GameEnds(GameIsDrawn, "Both players ran out of time",
11478                            GE_PLAYER);
11479                 else
11480                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11481             } else {
11482                 DisplayError(_("Your opponent is not out of time"), 0);
11483             }
11484             break;
11485           case MachinePlaysBlack:
11486             if (blackFlag) {
11487                 if (whiteFlag)
11488                   GameEnds(GameIsDrawn, "Both players ran out of time",
11489                            GE_PLAYER);
11490                 else
11491                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11492             } else {
11493                 DisplayError(_("Your opponent is not out of time"), 0);
11494             }
11495             break;
11496         }
11497     }
11498 }
11499
11500 void
11501 DrawEvent()
11502 {
11503     /* Offer draw or accept pending draw offer from opponent */
11504     
11505     if (appData.icsActive) {
11506         /* Note: tournament rules require draw offers to be
11507            made after you make your move but before you punch
11508            your clock.  Currently ICS doesn't let you do that;
11509            instead, you immediately punch your clock after making
11510            a move, but you can offer a draw at any time. */
11511         
11512         SendToICS(ics_prefix);
11513         SendToICS("draw\n");
11514     } else if (cmailMsgLoaded) {
11515         if (currentMove == cmailOldMove &&
11516             commentList[cmailOldMove] != NULL &&
11517             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11518                    "Black offers a draw" : "White offers a draw")) {
11519             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11520             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11521         } else if (currentMove == cmailOldMove + 1) {
11522             char *offer = WhiteOnMove(cmailOldMove) ?
11523               "White offers a draw" : "Black offers a draw";
11524             AppendComment(currentMove, offer, TRUE);
11525             DisplayComment(currentMove - 1, offer);
11526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11527         } else {
11528             DisplayError(_("You must make your move before offering a draw"), 0);
11529             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11530         }
11531     } else if (first.offeredDraw) {
11532         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11533     } else {
11534         if (first.sendDrawOffers) {
11535             SendToProgram("draw\n", &first);
11536             userOfferedDraw = TRUE;
11537         }
11538     }
11539 }
11540
11541 void
11542 AdjournEvent()
11543 {
11544     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11545     
11546     if (appData.icsActive) {
11547         SendToICS(ics_prefix);
11548         SendToICS("adjourn\n");
11549     } else {
11550         /* Currently GNU Chess doesn't offer or accept Adjourns */
11551     }
11552 }
11553
11554
11555 void
11556 AbortEvent()
11557 {
11558     /* Offer Abort or accept pending Abort offer from opponent */
11559     
11560     if (appData.icsActive) {
11561         SendToICS(ics_prefix);
11562         SendToICS("abort\n");
11563     } else {
11564         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11565     }
11566 }
11567
11568 void
11569 ResignEvent()
11570 {
11571     /* Resign.  You can do this even if it's not your turn. */
11572     
11573     if (appData.icsActive) {
11574         SendToICS(ics_prefix);
11575         SendToICS("resign\n");
11576     } else {
11577         switch (gameMode) {
11578           case MachinePlaysWhite:
11579             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11580             break;
11581           case MachinePlaysBlack:
11582             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11583             break;
11584           case EditGame:
11585             if (cmailMsgLoaded) {
11586                 TruncateGame();
11587                 if (WhiteOnMove(cmailOldMove)) {
11588                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11589                 } else {
11590                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11591                 }
11592                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11593             }
11594             break;
11595           default:
11596             break;
11597         }
11598     }
11599 }
11600
11601
11602 void
11603 StopObservingEvent()
11604 {
11605     /* Stop observing current games */
11606     SendToICS(ics_prefix);
11607     SendToICS("unobserve\n");
11608 }
11609
11610 void
11611 StopExaminingEvent()
11612 {
11613     /* Stop observing current game */
11614     SendToICS(ics_prefix);
11615     SendToICS("unexamine\n");
11616 }
11617
11618 void
11619 ForwardInner(target)
11620      int target;
11621 {
11622     int limit;
11623
11624     if (appData.debugMode)
11625         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11626                 target, currentMove, forwardMostMove);
11627
11628     if (gameMode == EditPosition)
11629       return;
11630
11631     if (gameMode == PlayFromGameFile && !pausing)
11632       PauseEvent();
11633     
11634     if (gameMode == IcsExamining && pausing)
11635       limit = pauseExamForwardMostMove;
11636     else
11637       limit = forwardMostMove;
11638     
11639     if (target > limit) target = limit;
11640
11641     if (target > 0 && moveList[target - 1][0]) {
11642         int fromX, fromY, toX, toY;
11643         toX = moveList[target - 1][2] - AAA;
11644         toY = moveList[target - 1][3] - ONE;
11645         if (moveList[target - 1][1] == '@') {
11646             if (appData.highlightLastMove) {
11647                 SetHighlights(-1, -1, toX, toY);
11648             }
11649         } else {
11650             fromX = moveList[target - 1][0] - AAA;
11651             fromY = moveList[target - 1][1] - ONE;
11652             if (target == currentMove + 1) {
11653                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11654             }
11655             if (appData.highlightLastMove) {
11656                 SetHighlights(fromX, fromY, toX, toY);
11657             }
11658         }
11659     }
11660     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11661         gameMode == Training || gameMode == PlayFromGameFile || 
11662         gameMode == AnalyzeFile) {
11663         while (currentMove < target) {
11664             SendMoveToProgram(currentMove++, &first);
11665         }
11666     } else {
11667         currentMove = target;
11668     }
11669     
11670     if (gameMode == EditGame || gameMode == EndOfGame) {
11671         whiteTimeRemaining = timeRemaining[0][currentMove];
11672         blackTimeRemaining = timeRemaining[1][currentMove];
11673     }
11674     DisplayBothClocks();
11675     DisplayMove(currentMove - 1);
11676     DrawPosition(FALSE, boards[currentMove]);
11677     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11678     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11679         DisplayComment(currentMove - 1, commentList[currentMove]);
11680     }
11681 }
11682
11683
11684 void
11685 ForwardEvent()
11686 {
11687     if (gameMode == IcsExamining && !pausing) {
11688         SendToICS(ics_prefix);
11689         SendToICS("forward\n");
11690     } else {
11691         ForwardInner(currentMove + 1);
11692     }
11693 }
11694
11695 void
11696 ToEndEvent()
11697 {
11698     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11699         /* to optimze, we temporarily turn off analysis mode while we feed
11700          * the remaining moves to the engine. Otherwise we get analysis output
11701          * after each move.
11702          */ 
11703         if (first.analysisSupport) {
11704           SendToProgram("exit\nforce\n", &first);
11705           first.analyzing = FALSE;
11706         }
11707     }
11708         
11709     if (gameMode == IcsExamining && !pausing) {
11710         SendToICS(ics_prefix);
11711         SendToICS("forward 999999\n");
11712     } else {
11713         ForwardInner(forwardMostMove);
11714     }
11715
11716     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11717         /* we have fed all the moves, so reactivate analysis mode */
11718         SendToProgram("analyze\n", &first);
11719         first.analyzing = TRUE;
11720         /*first.maybeThinking = TRUE;*/
11721         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11722     }
11723 }
11724
11725 void
11726 BackwardInner(target)
11727      int target;
11728 {
11729     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11730
11731     if (appData.debugMode)
11732         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11733                 target, currentMove, forwardMostMove);
11734
11735     if (gameMode == EditPosition) return;
11736     if (currentMove <= backwardMostMove) {
11737         ClearHighlights();
11738         DrawPosition(full_redraw, boards[currentMove]);
11739         return;
11740     }
11741     if (gameMode == PlayFromGameFile && !pausing)
11742       PauseEvent();
11743     
11744     if (moveList[target][0]) {
11745         int fromX, fromY, toX, toY;
11746         toX = moveList[target][2] - AAA;
11747         toY = moveList[target][3] - ONE;
11748         if (moveList[target][1] == '@') {
11749             if (appData.highlightLastMove) {
11750                 SetHighlights(-1, -1, toX, toY);
11751             }
11752         } else {
11753             fromX = moveList[target][0] - AAA;
11754             fromY = moveList[target][1] - ONE;
11755             if (target == currentMove - 1) {
11756                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11757             }
11758             if (appData.highlightLastMove) {
11759                 SetHighlights(fromX, fromY, toX, toY);
11760             }
11761         }
11762     }
11763     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11764         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11765         while (currentMove > target) {
11766             SendToProgram("undo\n", &first);
11767             currentMove--;
11768         }
11769     } else {
11770         currentMove = target;
11771     }
11772     
11773     if (gameMode == EditGame || gameMode == EndOfGame) {
11774         whiteTimeRemaining = timeRemaining[0][currentMove];
11775         blackTimeRemaining = timeRemaining[1][currentMove];
11776     }
11777     DisplayBothClocks();
11778     DisplayMove(currentMove - 1);
11779     DrawPosition(full_redraw, boards[currentMove]);
11780     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11781     // [HGM] PV info: routine tests if comment empty
11782     DisplayComment(currentMove - 1, commentList[currentMove]);
11783 }
11784
11785 void
11786 BackwardEvent()
11787 {
11788     if (gameMode == IcsExamining && !pausing) {
11789         SendToICS(ics_prefix);
11790         SendToICS("backward\n");
11791     } else {
11792         BackwardInner(currentMove - 1);
11793     }
11794 }
11795
11796 void
11797 ToStartEvent()
11798 {
11799     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11800         /* to optimze, we temporarily turn off analysis mode while we undo
11801          * all the moves. Otherwise we get analysis output after each undo.
11802          */ 
11803         if (first.analysisSupport) {
11804           SendToProgram("exit\nforce\n", &first);
11805           first.analyzing = FALSE;
11806         }
11807     }
11808
11809     if (gameMode == IcsExamining && !pausing) {
11810         SendToICS(ics_prefix);
11811         SendToICS("backward 999999\n");
11812     } else {
11813         BackwardInner(backwardMostMove);
11814     }
11815
11816     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11817         /* we have fed all the moves, so reactivate analysis mode */
11818         SendToProgram("analyze\n", &first);
11819         first.analyzing = TRUE;
11820         /*first.maybeThinking = TRUE;*/
11821         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11822     }
11823 }
11824
11825 void
11826 ToNrEvent(int to)
11827 {
11828   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11829   if (to >= forwardMostMove) to = forwardMostMove;
11830   if (to <= backwardMostMove) to = backwardMostMove;
11831   if (to < currentMove) {
11832     BackwardInner(to);
11833   } else {
11834     ForwardInner(to);
11835   }
11836 }
11837
11838 void
11839 RevertEvent()
11840 {
11841     if(PopTail()) { // [HGM] vari: restore old game tail
11842         return;
11843     }
11844     if (gameMode != IcsExamining) {
11845         DisplayError(_("You are not examining a game"), 0);
11846         return;
11847     }
11848     if (pausing) {
11849         DisplayError(_("You can't revert while pausing"), 0);
11850         return;
11851     }
11852     SendToICS(ics_prefix);
11853     SendToICS("revert\n");
11854 }
11855
11856 void
11857 RetractMoveEvent()
11858 {
11859     switch (gameMode) {
11860       case MachinePlaysWhite:
11861       case MachinePlaysBlack:
11862         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11863             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11864             return;
11865         }
11866         if (forwardMostMove < 2) return;
11867         currentMove = forwardMostMove = forwardMostMove - 2;
11868         whiteTimeRemaining = timeRemaining[0][currentMove];
11869         blackTimeRemaining = timeRemaining[1][currentMove];
11870         DisplayBothClocks();
11871         DisplayMove(currentMove - 1);
11872         ClearHighlights();/*!! could figure this out*/
11873         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11874         SendToProgram("remove\n", &first);
11875         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11876         break;
11877
11878       case BeginningOfGame:
11879       default:
11880         break;
11881
11882       case IcsPlayingWhite:
11883       case IcsPlayingBlack:
11884         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11885             SendToICS(ics_prefix);
11886             SendToICS("takeback 2\n");
11887         } else {
11888             SendToICS(ics_prefix);
11889             SendToICS("takeback 1\n");
11890         }
11891         break;
11892     }
11893 }
11894
11895 void
11896 MoveNowEvent()
11897 {
11898     ChessProgramState *cps;
11899
11900     switch (gameMode) {
11901       case MachinePlaysWhite:
11902         if (!WhiteOnMove(forwardMostMove)) {
11903             DisplayError(_("It is your turn"), 0);
11904             return;
11905         }
11906         cps = &first;
11907         break;
11908       case MachinePlaysBlack:
11909         if (WhiteOnMove(forwardMostMove)) {
11910             DisplayError(_("It is your turn"), 0);
11911             return;
11912         }
11913         cps = &first;
11914         break;
11915       case TwoMachinesPlay:
11916         if (WhiteOnMove(forwardMostMove) ==
11917             (first.twoMachinesColor[0] == 'w')) {
11918             cps = &first;
11919         } else {
11920             cps = &second;
11921         }
11922         break;
11923       case BeginningOfGame:
11924       default:
11925         return;
11926     }
11927     SendToProgram("?\n", cps);
11928 }
11929
11930 void
11931 TruncateGameEvent()
11932 {
11933     EditGameEvent();
11934     if (gameMode != EditGame) return;
11935     TruncateGame();
11936 }
11937
11938 void
11939 TruncateGame()
11940 {
11941     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11942     if (forwardMostMove > currentMove) {
11943         if (gameInfo.resultDetails != NULL) {
11944             free(gameInfo.resultDetails);
11945             gameInfo.resultDetails = NULL;
11946             gameInfo.result = GameUnfinished;
11947         }
11948         forwardMostMove = currentMove;
11949         HistorySet(parseList, backwardMostMove, forwardMostMove,
11950                    currentMove-1);
11951     }
11952 }
11953
11954 void
11955 HintEvent()
11956 {
11957     if (appData.noChessProgram) return;
11958     switch (gameMode) {
11959       case MachinePlaysWhite:
11960         if (WhiteOnMove(forwardMostMove)) {
11961             DisplayError(_("Wait until your turn"), 0);
11962             return;
11963         }
11964         break;
11965       case BeginningOfGame:
11966       case MachinePlaysBlack:
11967         if (!WhiteOnMove(forwardMostMove)) {
11968             DisplayError(_("Wait until your turn"), 0);
11969             return;
11970         }
11971         break;
11972       default:
11973         DisplayError(_("No hint available"), 0);
11974         return;
11975     }
11976     SendToProgram("hint\n", &first);
11977     hintRequested = TRUE;
11978 }
11979
11980 void
11981 BookEvent()
11982 {
11983     if (appData.noChessProgram) return;
11984     switch (gameMode) {
11985       case MachinePlaysWhite:
11986         if (WhiteOnMove(forwardMostMove)) {
11987             DisplayError(_("Wait until your turn"), 0);
11988             return;
11989         }
11990         break;
11991       case BeginningOfGame:
11992       case MachinePlaysBlack:
11993         if (!WhiteOnMove(forwardMostMove)) {
11994             DisplayError(_("Wait until your turn"), 0);
11995             return;
11996         }
11997         break;
11998       case EditPosition:
11999         EditPositionDone(TRUE);
12000         break;
12001       case TwoMachinesPlay:
12002         return;
12003       default:
12004         break;
12005     }
12006     SendToProgram("bk\n", &first);
12007     bookOutput[0] = NULLCHAR;
12008     bookRequested = TRUE;
12009 }
12010
12011 void
12012 AboutGameEvent()
12013 {
12014     char *tags = PGNTags(&gameInfo);
12015     TagsPopUp(tags, CmailMsg());
12016     free(tags);
12017 }
12018
12019 /* end button procedures */
12020
12021 void
12022 PrintPosition(fp, move)
12023      FILE *fp;
12024      int move;
12025 {
12026     int i, j;
12027     
12028     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12029         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12030             char c = PieceToChar(boards[move][i][j]);
12031             fputc(c == 'x' ? '.' : c, fp);
12032             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12033         }
12034     }
12035     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12036       fprintf(fp, "white to play\n");
12037     else
12038       fprintf(fp, "black to play\n");
12039 }
12040
12041 void
12042 PrintOpponents(fp)
12043      FILE *fp;
12044 {
12045     if (gameInfo.white != NULL) {
12046         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12047     } else {
12048         fprintf(fp, "\n");
12049     }
12050 }
12051
12052 /* Find last component of program's own name, using some heuristics */
12053 void
12054 TidyProgramName(prog, host, buf)
12055      char *prog, *host, buf[MSG_SIZ];
12056 {
12057     char *p, *q;
12058     int local = (strcmp(host, "localhost") == 0);
12059     while (!local && (p = strchr(prog, ';')) != NULL) {
12060         p++;
12061         while (*p == ' ') p++;
12062         prog = p;
12063     }
12064     if (*prog == '"' || *prog == '\'') {
12065         q = strchr(prog + 1, *prog);
12066     } else {
12067         q = strchr(prog, ' ');
12068     }
12069     if (q == NULL) q = prog + strlen(prog);
12070     p = q;
12071     while (p >= prog && *p != '/' && *p != '\\') p--;
12072     p++;
12073     if(p == prog && *p == '"') p++;
12074     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12075     memcpy(buf, p, q - p);
12076     buf[q - p] = NULLCHAR;
12077     if (!local) {
12078         strcat(buf, "@");
12079         strcat(buf, host);
12080     }
12081 }
12082
12083 char *
12084 TimeControlTagValue()
12085 {
12086     char buf[MSG_SIZ];
12087     if (!appData.clockMode) {
12088         strcpy(buf, "-");
12089     } else if (movesPerSession > 0) {
12090         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12091     } else if (timeIncrement == 0) {
12092         sprintf(buf, "%ld", timeControl/1000);
12093     } else {
12094         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12095     }
12096     return StrSave(buf);
12097 }
12098
12099 void
12100 SetGameInfo()
12101 {
12102     /* This routine is used only for certain modes */
12103     VariantClass v = gameInfo.variant;
12104     ClearGameInfo(&gameInfo);
12105     gameInfo.variant = v;
12106
12107     switch (gameMode) {
12108       case MachinePlaysWhite:
12109         gameInfo.event = StrSave( appData.pgnEventHeader );
12110         gameInfo.site = StrSave(HostName());
12111         gameInfo.date = PGNDate();
12112         gameInfo.round = StrSave("-");
12113         gameInfo.white = StrSave(first.tidy);
12114         gameInfo.black = StrSave(UserName());
12115         gameInfo.timeControl = TimeControlTagValue();
12116         break;
12117
12118       case MachinePlaysBlack:
12119         gameInfo.event = StrSave( appData.pgnEventHeader );
12120         gameInfo.site = StrSave(HostName());
12121         gameInfo.date = PGNDate();
12122         gameInfo.round = StrSave("-");
12123         gameInfo.white = StrSave(UserName());
12124         gameInfo.black = StrSave(first.tidy);
12125         gameInfo.timeControl = TimeControlTagValue();
12126         break;
12127
12128       case TwoMachinesPlay:
12129         gameInfo.event = StrSave( appData.pgnEventHeader );
12130         gameInfo.site = StrSave(HostName());
12131         gameInfo.date = PGNDate();
12132         if (matchGame > 0) {
12133             char buf[MSG_SIZ];
12134             sprintf(buf, "%d", matchGame);
12135             gameInfo.round = StrSave(buf);
12136         } else {
12137             gameInfo.round = StrSave("-");
12138         }
12139         if (first.twoMachinesColor[0] == 'w') {
12140             gameInfo.white = StrSave(first.tidy);
12141             gameInfo.black = StrSave(second.tidy);
12142         } else {
12143             gameInfo.white = StrSave(second.tidy);
12144             gameInfo.black = StrSave(first.tidy);
12145         }
12146         gameInfo.timeControl = TimeControlTagValue();
12147         break;
12148
12149       case EditGame:
12150         gameInfo.event = StrSave("Edited game");
12151         gameInfo.site = StrSave(HostName());
12152         gameInfo.date = PGNDate();
12153         gameInfo.round = StrSave("-");
12154         gameInfo.white = StrSave("-");
12155         gameInfo.black = StrSave("-");
12156         break;
12157
12158       case EditPosition:
12159         gameInfo.event = StrSave("Edited position");
12160         gameInfo.site = StrSave(HostName());
12161         gameInfo.date = PGNDate();
12162         gameInfo.round = StrSave("-");
12163         gameInfo.white = StrSave("-");
12164         gameInfo.black = StrSave("-");
12165         break;
12166
12167       case IcsPlayingWhite:
12168       case IcsPlayingBlack:
12169       case IcsObserving:
12170       case IcsExamining:
12171         break;
12172
12173       case PlayFromGameFile:
12174         gameInfo.event = StrSave("Game from non-PGN file");
12175         gameInfo.site = StrSave(HostName());
12176         gameInfo.date = PGNDate();
12177         gameInfo.round = StrSave("-");
12178         gameInfo.white = StrSave("?");
12179         gameInfo.black = StrSave("?");
12180         break;
12181
12182       default:
12183         break;
12184     }
12185 }
12186
12187 void
12188 ReplaceComment(index, text)
12189      int index;
12190      char *text;
12191 {
12192     int len;
12193
12194     while (*text == '\n') text++;
12195     len = strlen(text);
12196     while (len > 0 && text[len - 1] == '\n') len--;
12197
12198     if (commentList[index] != NULL)
12199       free(commentList[index]);
12200
12201     if (len == 0) {
12202         commentList[index] = NULL;
12203         return;
12204     }
12205   if(*text == '{' || *text == '(' || *text == '[') {
12206     commentList[index] = (char *) malloc(len + 2);
12207     strncpy(commentList[index], text, len);
12208     commentList[index][len] = '\n';
12209     commentList[index][len + 1] = NULLCHAR;
12210   } else { 
12211     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12212     char *p;
12213     commentList[index] = (char *) malloc(len + 6);
12214     strcpy(commentList[index], "{\n");
12215     strcat(commentList[index], text);
12216     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12217     strcat(commentList[index], "\n}");
12218   }
12219 }
12220
12221 void
12222 CrushCRs(text)
12223      char *text;
12224 {
12225   char *p = text;
12226   char *q = text;
12227   char ch;
12228
12229   do {
12230     ch = *p++;
12231     if (ch == '\r') continue;
12232     *q++ = ch;
12233   } while (ch != '\0');
12234 }
12235
12236 void
12237 AppendComment(index, text, addBraces)
12238      int index;
12239      char *text;
12240      Boolean addBraces; // [HGM] braces: tells if we should add {}
12241 {
12242     int oldlen, len;
12243     char *old;
12244
12245 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12246     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12247
12248     CrushCRs(text);
12249     while (*text == '\n') text++;
12250     len = strlen(text);
12251     while (len > 0 && text[len - 1] == '\n') len--;
12252
12253     if (len == 0) return;
12254
12255     if (commentList[index] != NULL) {
12256         old = commentList[index];
12257         oldlen = strlen(old);
12258         commentList[index] = (char *) malloc(oldlen + len + 4); // might waste 2
12259         strcpy(commentList[index], old);
12260         free(old);
12261         // [HGM] braces: join "{A\n}" + "{B}" as "{A\nB\n}"
12262         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12263           if(addBraces) addBraces = FALSE; else { text++; len--; }
12264           while (*text == '\n') { text++; len--; }
12265           commentList[index][oldlen-1] = NULLCHAR;
12266           oldlen--;
12267       }
12268         strncpy(&commentList[index][oldlen], text, len);
12269         if(addBraces) strcpy(&commentList[index][oldlen + len], "\n}");
12270         else          strcpy(&commentList[index][oldlen + len], "\n");
12271     } else {
12272         commentList[index] = (char *) malloc(len + 4); // perhaps wastes 2...
12273         if(addBraces) commentList[index][0] = '{';
12274         strcpy(commentList[index] + addBraces, text);
12275         strcat(commentList[index], "\n");
12276         if(addBraces) strcat(commentList[index], "}");
12277     }
12278 }
12279
12280 static char * FindStr( char * text, char * sub_text )
12281 {
12282     char * result = strstr( text, sub_text );
12283
12284     if( result != NULL ) {
12285         result += strlen( sub_text );
12286     }
12287
12288     return result;
12289 }
12290
12291 /* [AS] Try to extract PV info from PGN comment */
12292 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12293 char *GetInfoFromComment( int index, char * text )
12294 {
12295     char * sep = text;
12296
12297     if( text != NULL && index > 0 ) {
12298         int score = 0;
12299         int depth = 0;
12300         int time = -1, sec = 0, deci;
12301         char * s_eval = FindStr( text, "[%eval " );
12302         char * s_emt = FindStr( text, "[%emt " );
12303
12304         if( s_eval != NULL || s_emt != NULL ) {
12305             /* New style */
12306             char delim;
12307
12308             if( s_eval != NULL ) {
12309                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12310                     return text;
12311                 }
12312
12313                 if( delim != ']' ) {
12314                     return text;
12315                 }
12316             }
12317
12318             if( s_emt != NULL ) {
12319             }
12320                 return text;
12321         }
12322         else {
12323             /* We expect something like: [+|-]nnn.nn/dd */
12324             int score_lo = 0;
12325
12326             if(*text != '{') return text; // [HGM] braces: must be normal comment
12327
12328             sep = strchr( text, '/' );
12329             if( sep == NULL || sep < (text+4) ) {
12330                 return text;
12331             }
12332
12333             time = -1; sec = -1; deci = -1;
12334             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12335                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12336                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12337                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12338                 return text;
12339             }
12340
12341             if( score_lo < 0 || score_lo >= 100 ) {
12342                 return text;
12343             }
12344
12345             if(sec >= 0) time = 600*time + 10*sec; else
12346             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12347
12348             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12349
12350             /* [HGM] PV time: now locate end of PV info */
12351             while( *++sep >= '0' && *sep <= '9'); // strip depth
12352             if(time >= 0)
12353             while( *++sep >= '0' && *sep <= '9'); // strip time
12354             if(sec >= 0)
12355             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12356             if(deci >= 0)
12357             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12358             while(*sep == ' ') sep++;
12359         }
12360
12361         if( depth <= 0 ) {
12362             return text;
12363         }
12364
12365         if( time < 0 ) {
12366             time = -1;
12367         }
12368
12369         pvInfoList[index-1].depth = depth;
12370         pvInfoList[index-1].score = score;
12371         pvInfoList[index-1].time  = 10*time; // centi-sec
12372         if(*sep == '}') *sep = 0; else *--sep = '{';
12373     }
12374     return sep;
12375 }
12376
12377 void
12378 SendToProgram(message, cps)
12379      char *message;
12380      ChessProgramState *cps;
12381 {
12382     int count, outCount, error;
12383     char buf[MSG_SIZ];
12384
12385     if (cps->pr == NULL) return;
12386     Attention(cps);
12387     
12388     if (appData.debugMode) {
12389         TimeMark now;
12390         GetTimeMark(&now);
12391         fprintf(debugFP, "%ld >%-6s: %s", 
12392                 SubtractTimeMarks(&now, &programStartTime),
12393                 cps->which, message);
12394     }
12395     
12396     count = strlen(message);
12397     outCount = OutputToProcess(cps->pr, message, count, &error);
12398     if (outCount < count && !exiting 
12399                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12400         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12401         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12402             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12403                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12404                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12405             } else {
12406                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12407             }
12408             gameInfo.resultDetails = buf;
12409         }
12410         DisplayFatalError(buf, error, 1);
12411     }
12412 }
12413
12414 void
12415 ReceiveFromProgram(isr, closure, message, count, error)
12416      InputSourceRef isr;
12417      VOIDSTAR closure;
12418      char *message;
12419      int count;
12420      int error;
12421 {
12422     char *end_str;
12423     char buf[MSG_SIZ];
12424     ChessProgramState *cps = (ChessProgramState *)closure;
12425
12426     if (isr != cps->isr) return; /* Killed intentionally */
12427     if (count <= 0) {
12428         if (count == 0) {
12429             sprintf(buf,
12430                     _("Error: %s chess program (%s) exited unexpectedly"),
12431                     cps->which, cps->program);
12432         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12433                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12434                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12435                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12436                 } else {
12437                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12438                 }
12439                 gameInfo.resultDetails = buf;
12440             }
12441             RemoveInputSource(cps->isr);
12442             DisplayFatalError(buf, 0, 1);
12443         } else {
12444             sprintf(buf,
12445                     _("Error reading from %s chess program (%s)"),
12446                     cps->which, cps->program);
12447             RemoveInputSource(cps->isr);
12448
12449             /* [AS] Program is misbehaving badly... kill it */
12450             if( count == -2 ) {
12451                 DestroyChildProcess( cps->pr, 9 );
12452                 cps->pr = NoProc;
12453             }
12454
12455             DisplayFatalError(buf, error, 1);
12456         }
12457         return;
12458     }
12459     
12460     if ((end_str = strchr(message, '\r')) != NULL)
12461       *end_str = NULLCHAR;
12462     if ((end_str = strchr(message, '\n')) != NULL)
12463       *end_str = NULLCHAR;
12464     
12465     if (appData.debugMode) {
12466         TimeMark now; int print = 1;
12467         char *quote = ""; char c; int i;
12468
12469         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12470                 char start = message[0];
12471                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12472                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12473                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12474                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12475                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12476                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12477                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12478                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12479                         { quote = "# "; print = (appData.engineComments == 2); }
12480                 message[0] = start; // restore original message
12481         }
12482         if(print) {
12483                 GetTimeMark(&now);
12484                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12485                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12486                         quote,
12487                         message);
12488         }
12489     }
12490
12491     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12492     if (appData.icsEngineAnalyze) {
12493         if (strstr(message, "whisper") != NULL ||
12494              strstr(message, "kibitz") != NULL || 
12495             strstr(message, "tellics") != NULL) return;
12496     }
12497
12498     HandleMachineMove(message, cps);
12499 }
12500
12501
12502 void
12503 SendTimeControl(cps, mps, tc, inc, sd, st)
12504      ChessProgramState *cps;
12505      int mps, inc, sd, st;
12506      long tc;
12507 {
12508     char buf[MSG_SIZ];
12509     int seconds;
12510
12511     if( timeControl_2 > 0 ) {
12512         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12513             tc = timeControl_2;
12514         }
12515     }
12516     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12517     inc /= cps->timeOdds;
12518     st  /= cps->timeOdds;
12519
12520     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12521
12522     if (st > 0) {
12523       /* Set exact time per move, normally using st command */
12524       if (cps->stKludge) {
12525         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12526         seconds = st % 60;
12527         if (seconds == 0) {
12528           sprintf(buf, "level 1 %d\n", st/60);
12529         } else {
12530           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12531         }
12532       } else {
12533         sprintf(buf, "st %d\n", st);
12534       }
12535     } else {
12536       /* Set conventional or incremental time control, using level command */
12537       if (seconds == 0) {
12538         /* Note old gnuchess bug -- minutes:seconds used to not work.
12539            Fixed in later versions, but still avoid :seconds
12540            when seconds is 0. */
12541         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12542       } else {
12543         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12544                 seconds, inc/1000);
12545       }
12546     }
12547     SendToProgram(buf, cps);
12548
12549     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12550     /* Orthogonally, limit search to given depth */
12551     if (sd > 0) {
12552       if (cps->sdKludge) {
12553         sprintf(buf, "depth\n%d\n", sd);
12554       } else {
12555         sprintf(buf, "sd %d\n", sd);
12556       }
12557       SendToProgram(buf, cps);
12558     }
12559
12560     if(cps->nps > 0) { /* [HGM] nps */
12561         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12562         else {
12563                 sprintf(buf, "nps %d\n", cps->nps);
12564               SendToProgram(buf, cps);
12565         }
12566     }
12567 }
12568
12569 ChessProgramState *WhitePlayer()
12570 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12571 {
12572     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12573        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12574         return &second;
12575     return &first;
12576 }
12577
12578 void
12579 SendTimeRemaining(cps, machineWhite)
12580      ChessProgramState *cps;
12581      int /*boolean*/ machineWhite;
12582 {
12583     char message[MSG_SIZ];
12584     long time, otime;
12585
12586     /* Note: this routine must be called when the clocks are stopped
12587        or when they have *just* been set or switched; otherwise
12588        it will be off by the time since the current tick started.
12589     */
12590     if (machineWhite) {
12591         time = whiteTimeRemaining / 10;
12592         otime = blackTimeRemaining / 10;
12593     } else {
12594         time = blackTimeRemaining / 10;
12595         otime = whiteTimeRemaining / 10;
12596     }
12597     /* [HGM] translate opponent's time by time-odds factor */
12598     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12599     if (appData.debugMode) {
12600         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12601     }
12602
12603     if (time <= 0) time = 1;
12604     if (otime <= 0) otime = 1;
12605     
12606     sprintf(message, "time %ld\n", time);
12607     SendToProgram(message, cps);
12608
12609     sprintf(message, "otim %ld\n", otime);
12610     SendToProgram(message, cps);
12611 }
12612
12613 int
12614 BoolFeature(p, name, loc, cps)
12615      char **p;
12616      char *name;
12617      int *loc;
12618      ChessProgramState *cps;
12619 {
12620   char buf[MSG_SIZ];
12621   int len = strlen(name);
12622   int val;
12623   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12624     (*p) += len + 1;
12625     sscanf(*p, "%d", &val);
12626     *loc = (val != 0);
12627     while (**p && **p != ' ') (*p)++;
12628     sprintf(buf, "accepted %s\n", name);
12629     SendToProgram(buf, cps);
12630     return TRUE;
12631   }
12632   return FALSE;
12633 }
12634
12635 int
12636 IntFeature(p, name, loc, cps)
12637      char **p;
12638      char *name;
12639      int *loc;
12640      ChessProgramState *cps;
12641 {
12642   char buf[MSG_SIZ];
12643   int len = strlen(name);
12644   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12645     (*p) += len + 1;
12646     sscanf(*p, "%d", loc);
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 StringFeature(p, name, loc, cps)
12657      char **p;
12658      char *name;
12659      char loc[];
12660      ChessProgramState *cps;
12661 {
12662   char buf[MSG_SIZ];
12663   int len = strlen(name);
12664   if (strncmp((*p), name, len) == 0
12665       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12666     (*p) += len + 2;
12667     sscanf(*p, "%[^\"]", loc);
12668     while (**p && **p != '\"') (*p)++;
12669     if (**p == '\"') (*p)++;
12670     sprintf(buf, "accepted %s\n", name);
12671     SendToProgram(buf, cps);
12672     return TRUE;
12673   }
12674   return FALSE;
12675 }
12676
12677 int 
12678 ParseOption(Option *opt, ChessProgramState *cps)
12679 // [HGM] options: process the string that defines an engine option, and determine
12680 // name, type, default value, and allowed value range
12681 {
12682         char *p, *q, buf[MSG_SIZ];
12683         int n, min = (-1)<<31, max = 1<<31, def;
12684
12685         if(p = strstr(opt->name, " -spin ")) {
12686             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12687             if(max < min) max = min; // enforce consistency
12688             if(def < min) def = min;
12689             if(def > max) def = max;
12690             opt->value = def;
12691             opt->min = min;
12692             opt->max = max;
12693             opt->type = Spin;
12694         } else if((p = strstr(opt->name, " -slider "))) {
12695             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12696             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12697             if(max < min) max = min; // enforce consistency
12698             if(def < min) def = min;
12699             if(def > max) def = max;
12700             opt->value = def;
12701             opt->min = min;
12702             opt->max = max;
12703             opt->type = Spin; // Slider;
12704         } else if((p = strstr(opt->name, " -string "))) {
12705             opt->textValue = p+9;
12706             opt->type = TextBox;
12707         } else if((p = strstr(opt->name, " -file "))) {
12708             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12709             opt->textValue = p+7;
12710             opt->type = TextBox; // FileName;
12711         } else if((p = strstr(opt->name, " -path "))) {
12712             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12713             opt->textValue = p+7;
12714             opt->type = TextBox; // PathName;
12715         } else if(p = strstr(opt->name, " -check ")) {
12716             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12717             opt->value = (def != 0);
12718             opt->type = CheckBox;
12719         } else if(p = strstr(opt->name, " -combo ")) {
12720             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12721             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12722             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12723             opt->value = n = 0;
12724             while(q = StrStr(q, " /// ")) {
12725                 n++; *q = 0;    // count choices, and null-terminate each of them
12726                 q += 5;
12727                 if(*q == '*') { // remember default, which is marked with * prefix
12728                     q++;
12729                     opt->value = n;
12730                 }
12731                 cps->comboList[cps->comboCnt++] = q;
12732             }
12733             cps->comboList[cps->comboCnt++] = NULL;
12734             opt->max = n + 1;
12735             opt->type = ComboBox;
12736         } else if(p = strstr(opt->name, " -button")) {
12737             opt->type = Button;
12738         } else if(p = strstr(opt->name, " -save")) {
12739             opt->type = SaveButton;
12740         } else return FALSE;
12741         *p = 0; // terminate option name
12742         // now look if the command-line options define a setting for this engine option.
12743         if(cps->optionSettings && cps->optionSettings[0])
12744             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12745         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12746                 sprintf(buf, "option %s", p);
12747                 if(p = strstr(buf, ",")) *p = 0;
12748                 strcat(buf, "\n");
12749                 SendToProgram(buf, cps);
12750         }
12751         return TRUE;
12752 }
12753
12754 void
12755 FeatureDone(cps, val)
12756      ChessProgramState* cps;
12757      int val;
12758 {
12759   DelayedEventCallback cb = GetDelayedEvent();
12760   if ((cb == InitBackEnd3 && cps == &first) ||
12761       (cb == TwoMachinesEventIfReady && cps == &second)) {
12762     CancelDelayedEvent();
12763     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12764   }
12765   cps->initDone = val;
12766 }
12767
12768 /* Parse feature command from engine */
12769 void
12770 ParseFeatures(args, cps)
12771      char* args;
12772      ChessProgramState *cps;  
12773 {
12774   char *p = args;
12775   char *q;
12776   int val;
12777   char buf[MSG_SIZ];
12778
12779   for (;;) {
12780     while (*p == ' ') p++;
12781     if (*p == NULLCHAR) return;
12782
12783     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12784     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12785     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12786     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12787     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12788     if (BoolFeature(&p, "reuse", &val, cps)) {
12789       /* Engine can disable reuse, but can't enable it if user said no */
12790       if (!val) cps->reuse = FALSE;
12791       continue;
12792     }
12793     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12794     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12795       if (gameMode == TwoMachinesPlay) {
12796         DisplayTwoMachinesTitle();
12797       } else {
12798         DisplayTitle("");
12799       }
12800       continue;
12801     }
12802     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12803     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12804     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12805     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12806     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12807     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12808     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12809     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12810     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12811     if (IntFeature(&p, "done", &val, cps)) {
12812       FeatureDone(cps, val);
12813       continue;
12814     }
12815     /* Added by Tord: */
12816     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12817     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12818     /* End of additions by Tord */
12819
12820     /* [HGM] added features: */
12821     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12822     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12823     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12824     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12825     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12826     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12827     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12828         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12829             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12830             SendToProgram(buf, cps);
12831             continue;
12832         }
12833         if(cps->nrOptions >= MAX_OPTIONS) {
12834             cps->nrOptions--;
12835             sprintf(buf, "%s engine has too many options\n", cps->which);
12836             DisplayError(buf, 0);
12837         }
12838         continue;
12839     }
12840     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12841     /* End of additions by HGM */
12842
12843     /* unknown feature: complain and skip */
12844     q = p;
12845     while (*q && *q != '=') q++;
12846     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12847     SendToProgram(buf, cps);
12848     p = q;
12849     if (*p == '=') {
12850       p++;
12851       if (*p == '\"') {
12852         p++;
12853         while (*p && *p != '\"') p++;
12854         if (*p == '\"') p++;
12855       } else {
12856         while (*p && *p != ' ') p++;
12857       }
12858     }
12859   }
12860
12861 }
12862
12863 void
12864 PeriodicUpdatesEvent(newState)
12865      int newState;
12866 {
12867     if (newState == appData.periodicUpdates)
12868       return;
12869
12870     appData.periodicUpdates=newState;
12871
12872     /* Display type changes, so update it now */
12873 //    DisplayAnalysis();
12874
12875     /* Get the ball rolling again... */
12876     if (newState) {
12877         AnalysisPeriodicEvent(1);
12878         StartAnalysisClock();
12879     }
12880 }
12881
12882 void
12883 PonderNextMoveEvent(newState)
12884      int newState;
12885 {
12886     if (newState == appData.ponderNextMove) return;
12887     if (gameMode == EditPosition) EditPositionDone(TRUE);
12888     if (newState) {
12889         SendToProgram("hard\n", &first);
12890         if (gameMode == TwoMachinesPlay) {
12891             SendToProgram("hard\n", &second);
12892         }
12893     } else {
12894         SendToProgram("easy\n", &first);
12895         thinkOutput[0] = NULLCHAR;
12896         if (gameMode == TwoMachinesPlay) {
12897             SendToProgram("easy\n", &second);
12898         }
12899     }
12900     appData.ponderNextMove = newState;
12901 }
12902
12903 void
12904 NewSettingEvent(option, command, value)
12905      char *command;
12906      int option, value;
12907 {
12908     char buf[MSG_SIZ];
12909
12910     if (gameMode == EditPosition) EditPositionDone(TRUE);
12911     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12912     SendToProgram(buf, &first);
12913     if (gameMode == TwoMachinesPlay) {
12914         SendToProgram(buf, &second);
12915     }
12916 }
12917
12918 void
12919 ShowThinkingEvent()
12920 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12921 {
12922     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12923     int newState = appData.showThinking
12924         // [HGM] thinking: other features now need thinking output as well
12925         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12926     
12927     if (oldState == newState) return;
12928     oldState = newState;
12929     if (gameMode == EditPosition) EditPositionDone(TRUE);
12930     if (oldState) {
12931         SendToProgram("post\n", &first);
12932         if (gameMode == TwoMachinesPlay) {
12933             SendToProgram("post\n", &second);
12934         }
12935     } else {
12936         SendToProgram("nopost\n", &first);
12937         thinkOutput[0] = NULLCHAR;
12938         if (gameMode == TwoMachinesPlay) {
12939             SendToProgram("nopost\n", &second);
12940         }
12941     }
12942 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12943 }
12944
12945 void
12946 AskQuestionEvent(title, question, replyPrefix, which)
12947      char *title; char *question; char *replyPrefix; char *which;
12948 {
12949   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12950   if (pr == NoProc) return;
12951   AskQuestion(title, question, replyPrefix, pr);
12952 }
12953
12954 void
12955 DisplayMove(moveNumber)
12956      int moveNumber;
12957 {
12958     char message[MSG_SIZ];
12959     char res[MSG_SIZ];
12960     char cpThinkOutput[MSG_SIZ];
12961
12962     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12963     
12964     if (moveNumber == forwardMostMove - 1 || 
12965         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12966
12967         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12968
12969         if (strchr(cpThinkOutput, '\n')) {
12970             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12971         }
12972     } else {
12973         *cpThinkOutput = NULLCHAR;
12974     }
12975
12976     /* [AS] Hide thinking from human user */
12977     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12978         *cpThinkOutput = NULLCHAR;
12979         if( thinkOutput[0] != NULLCHAR ) {
12980             int i;
12981
12982             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12983                 cpThinkOutput[i] = '.';
12984             }
12985             cpThinkOutput[i] = NULLCHAR;
12986             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12987         }
12988     }
12989
12990     if (moveNumber == forwardMostMove - 1 &&
12991         gameInfo.resultDetails != NULL) {
12992         if (gameInfo.resultDetails[0] == NULLCHAR) {
12993             sprintf(res, " %s", PGNResult(gameInfo.result));
12994         } else {
12995             sprintf(res, " {%s} %s",
12996                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12997         }
12998     } else {
12999         res[0] = NULLCHAR;
13000     }
13001
13002     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13003         DisplayMessage(res, cpThinkOutput);
13004     } else {
13005         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13006                 WhiteOnMove(moveNumber) ? " " : ".. ",
13007                 parseList[moveNumber], res);
13008         DisplayMessage(message, cpThinkOutput);
13009     }
13010 }
13011
13012 void
13013 DisplayComment(moveNumber, text)
13014      int moveNumber;
13015      char *text;
13016 {
13017     char title[MSG_SIZ];
13018     char buf[8000]; // comment can be long!
13019     int score, depth;
13020     
13021     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13022       strcpy(title, "Comment");
13023     } else {
13024       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13025               WhiteOnMove(moveNumber) ? " " : ".. ",
13026               parseList[moveNumber]);
13027     }
13028     // [HGM] PV info: display PV info together with (or as) comment
13029     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13030       if(text == NULL) text = "";                                           
13031       score = pvInfoList[moveNumber].score;
13032       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13033               depth, (pvInfoList[moveNumber].time+50)/100, text);
13034       text = buf;
13035     }
13036     if (text != NULL && (appData.autoDisplayComment || commentUp))
13037         CommentPopUp(title, text);
13038 }
13039
13040 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13041  * might be busy thinking or pondering.  It can be omitted if your
13042  * gnuchess is configured to stop thinking immediately on any user
13043  * input.  However, that gnuchess feature depends on the FIONREAD
13044  * ioctl, which does not work properly on some flavors of Unix.
13045  */
13046 void
13047 Attention(cps)
13048      ChessProgramState *cps;
13049 {
13050 #if ATTENTION
13051     if (!cps->useSigint) return;
13052     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13053     switch (gameMode) {
13054       case MachinePlaysWhite:
13055       case MachinePlaysBlack:
13056       case TwoMachinesPlay:
13057       case IcsPlayingWhite:
13058       case IcsPlayingBlack:
13059       case AnalyzeMode:
13060       case AnalyzeFile:
13061         /* Skip if we know it isn't thinking */
13062         if (!cps->maybeThinking) return;
13063         if (appData.debugMode)
13064           fprintf(debugFP, "Interrupting %s\n", cps->which);
13065         InterruptChildProcess(cps->pr);
13066         cps->maybeThinking = FALSE;
13067         break;
13068       default:
13069         break;
13070     }
13071 #endif /*ATTENTION*/
13072 }
13073
13074 int
13075 CheckFlags()
13076 {
13077     if (whiteTimeRemaining <= 0) {
13078         if (!whiteFlag) {
13079             whiteFlag = TRUE;
13080             if (appData.icsActive) {
13081                 if (appData.autoCallFlag &&
13082                     gameMode == IcsPlayingBlack && !blackFlag) {
13083                   SendToICS(ics_prefix);
13084                   SendToICS("flag\n");
13085                 }
13086             } else {
13087                 if (blackFlag) {
13088                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13089                 } else {
13090                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13091                     if (appData.autoCallFlag) {
13092                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13093                         return TRUE;
13094                     }
13095                 }
13096             }
13097         }
13098     }
13099     if (blackTimeRemaining <= 0) {
13100         if (!blackFlag) {
13101             blackFlag = TRUE;
13102             if (appData.icsActive) {
13103                 if (appData.autoCallFlag &&
13104                     gameMode == IcsPlayingWhite && !whiteFlag) {
13105                   SendToICS(ics_prefix);
13106                   SendToICS("flag\n");
13107                 }
13108             } else {
13109                 if (whiteFlag) {
13110                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13111                 } else {
13112                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13113                     if (appData.autoCallFlag) {
13114                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13115                         return TRUE;
13116                     }
13117                 }
13118             }
13119         }
13120     }
13121     return FALSE;
13122 }
13123
13124 void
13125 CheckTimeControl()
13126 {
13127     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13128         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13129
13130     /*
13131      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13132      */
13133     if ( !WhiteOnMove(forwardMostMove) )
13134         /* White made time control */
13135         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13136         /* [HGM] time odds: correct new time quota for time odds! */
13137                                             / WhitePlayer()->timeOdds;
13138       else
13139         /* Black made time control */
13140         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13141                                             / WhitePlayer()->other->timeOdds;
13142 }
13143
13144 void
13145 DisplayBothClocks()
13146 {
13147     int wom = gameMode == EditPosition ?
13148       !blackPlaysFirst : WhiteOnMove(currentMove);
13149     DisplayWhiteClock(whiteTimeRemaining, wom);
13150     DisplayBlackClock(blackTimeRemaining, !wom);
13151 }
13152
13153
13154 /* Timekeeping seems to be a portability nightmare.  I think everyone
13155    has ftime(), but I'm really not sure, so I'm including some ifdefs
13156    to use other calls if you don't.  Clocks will be less accurate if
13157    you have neither ftime nor gettimeofday.
13158 */
13159
13160 /* VS 2008 requires the #include outside of the function */
13161 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13162 #include <sys/timeb.h>
13163 #endif
13164
13165 /* Get the current time as a TimeMark */
13166 void
13167 GetTimeMark(tm)
13168      TimeMark *tm;
13169 {
13170 #if HAVE_GETTIMEOFDAY
13171
13172     struct timeval timeVal;
13173     struct timezone timeZone;
13174
13175     gettimeofday(&timeVal, &timeZone);
13176     tm->sec = (long) timeVal.tv_sec; 
13177     tm->ms = (int) (timeVal.tv_usec / 1000L);
13178
13179 #else /*!HAVE_GETTIMEOFDAY*/
13180 #if HAVE_FTIME
13181
13182 // include <sys/timeb.h> / moved to just above start of function
13183     struct timeb timeB;
13184
13185     ftime(&timeB);
13186     tm->sec = (long) timeB.time;
13187     tm->ms = (int) timeB.millitm;
13188
13189 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13190     tm->sec = (long) time(NULL);
13191     tm->ms = 0;
13192 #endif
13193 #endif
13194 }
13195
13196 /* Return the difference in milliseconds between two
13197    time marks.  We assume the difference will fit in a long!
13198 */
13199 long
13200 SubtractTimeMarks(tm2, tm1)
13201      TimeMark *tm2, *tm1;
13202 {
13203     return 1000L*(tm2->sec - tm1->sec) +
13204            (long) (tm2->ms - tm1->ms);
13205 }
13206
13207
13208 /*
13209  * Code to manage the game clocks.
13210  *
13211  * In tournament play, black starts the clock and then white makes a move.
13212  * We give the human user a slight advantage if he is playing white---the
13213  * clocks don't run until he makes his first move, so it takes zero time.
13214  * Also, we don't account for network lag, so we could get out of sync
13215  * with GNU Chess's clock -- but then, referees are always right.  
13216  */
13217
13218 static TimeMark tickStartTM;
13219 static long intendedTickLength;
13220
13221 long
13222 NextTickLength(timeRemaining)
13223      long timeRemaining;
13224 {
13225     long nominalTickLength, nextTickLength;
13226
13227     if (timeRemaining > 0L && timeRemaining <= 10000L)
13228       nominalTickLength = 100L;
13229     else
13230       nominalTickLength = 1000L;
13231     nextTickLength = timeRemaining % nominalTickLength;
13232     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13233
13234     return nextTickLength;
13235 }
13236
13237 /* Adjust clock one minute up or down */
13238 void
13239 AdjustClock(Boolean which, int dir)
13240 {
13241     if(which) blackTimeRemaining += 60000*dir;
13242     else      whiteTimeRemaining += 60000*dir;
13243     DisplayBothClocks();
13244 }
13245
13246 /* Stop clocks and reset to a fresh time control */
13247 void
13248 ResetClocks() 
13249 {
13250     (void) StopClockTimer();
13251     if (appData.icsActive) {
13252         whiteTimeRemaining = blackTimeRemaining = 0;
13253     } else if (searchTime) {
13254         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13255         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13256     } else { /* [HGM] correct new time quote for time odds */
13257         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13258         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13259     }
13260     if (whiteFlag || blackFlag) {
13261         DisplayTitle("");
13262         whiteFlag = blackFlag = FALSE;
13263     }
13264     DisplayBothClocks();
13265 }
13266
13267 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13268
13269 /* Decrement running clock by amount of time that has passed */
13270 void
13271 DecrementClocks()
13272 {
13273     long timeRemaining;
13274     long lastTickLength, fudge;
13275     TimeMark now;
13276
13277     if (!appData.clockMode) return;
13278     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13279         
13280     GetTimeMark(&now);
13281
13282     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13283
13284     /* Fudge if we woke up a little too soon */
13285     fudge = intendedTickLength - lastTickLength;
13286     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13287
13288     if (WhiteOnMove(forwardMostMove)) {
13289         if(whiteNPS >= 0) lastTickLength = 0;
13290         timeRemaining = whiteTimeRemaining -= lastTickLength;
13291         DisplayWhiteClock(whiteTimeRemaining - fudge,
13292                           WhiteOnMove(currentMove));
13293     } else {
13294         if(blackNPS >= 0) lastTickLength = 0;
13295         timeRemaining = blackTimeRemaining -= lastTickLength;
13296         DisplayBlackClock(blackTimeRemaining - fudge,
13297                           !WhiteOnMove(currentMove));
13298     }
13299
13300     if (CheckFlags()) return;
13301         
13302     tickStartTM = now;
13303     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13304     StartClockTimer(intendedTickLength);
13305
13306     /* if the time remaining has fallen below the alarm threshold, sound the
13307      * alarm. if the alarm has sounded and (due to a takeback or time control
13308      * with increment) the time remaining has increased to a level above the
13309      * threshold, reset the alarm so it can sound again. 
13310      */
13311     
13312     if (appData.icsActive && appData.icsAlarm) {
13313
13314         /* make sure we are dealing with the user's clock */
13315         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13316                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13317            )) return;
13318
13319         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13320             alarmSounded = FALSE;
13321         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13322             PlayAlarmSound();
13323             alarmSounded = TRUE;
13324         }
13325     }
13326 }
13327
13328
13329 /* A player has just moved, so stop the previously running
13330    clock and (if in clock mode) start the other one.
13331    We redisplay both clocks in case we're in ICS mode, because
13332    ICS gives us an update to both clocks after every move.
13333    Note that this routine is called *after* forwardMostMove
13334    is updated, so the last fractional tick must be subtracted
13335    from the color that is *not* on move now.
13336 */
13337 void
13338 SwitchClocks()
13339 {
13340     long lastTickLength;
13341     TimeMark now;
13342     int flagged = FALSE;
13343
13344     GetTimeMark(&now);
13345
13346     if (StopClockTimer() && appData.clockMode) {
13347         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13348         if (WhiteOnMove(forwardMostMove)) {
13349             if(blackNPS >= 0) lastTickLength = 0;
13350             blackTimeRemaining -= lastTickLength;
13351            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13352 //         if(pvInfoList[forwardMostMove-1].time == -1)
13353                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13354                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13355         } else {
13356            if(whiteNPS >= 0) lastTickLength = 0;
13357            whiteTimeRemaining -= lastTickLength;
13358            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13359 //         if(pvInfoList[forwardMostMove-1].time == -1)
13360                  pvInfoList[forwardMostMove-1].time = 
13361                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13362         }
13363         flagged = CheckFlags();
13364     }
13365     CheckTimeControl();
13366
13367     if (flagged || !appData.clockMode) return;
13368
13369     switch (gameMode) {
13370       case MachinePlaysBlack:
13371       case MachinePlaysWhite:
13372       case BeginningOfGame:
13373         if (pausing) return;
13374         break;
13375
13376       case EditGame:
13377       case PlayFromGameFile:
13378       case IcsExamining:
13379         return;
13380
13381       default:
13382         break;
13383     }
13384
13385     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13386         if(WhiteOnMove(forwardMostMove))
13387              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13388         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13389     }
13390
13391     tickStartTM = now;
13392     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13393       whiteTimeRemaining : blackTimeRemaining);
13394     StartClockTimer(intendedTickLength);
13395 }
13396         
13397
13398 /* Stop both clocks */
13399 void
13400 StopClocks()
13401 {       
13402     long lastTickLength;
13403     TimeMark now;
13404
13405     if (!StopClockTimer()) return;
13406     if (!appData.clockMode) return;
13407
13408     GetTimeMark(&now);
13409
13410     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13411     if (WhiteOnMove(forwardMostMove)) {
13412         if(whiteNPS >= 0) lastTickLength = 0;
13413         whiteTimeRemaining -= lastTickLength;
13414         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13415     } else {
13416         if(blackNPS >= 0) lastTickLength = 0;
13417         blackTimeRemaining -= lastTickLength;
13418         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13419     }
13420     CheckFlags();
13421 }
13422         
13423 /* Start clock of player on move.  Time may have been reset, so
13424    if clock is already running, stop and restart it. */
13425 void
13426 StartClocks()
13427 {
13428     (void) StopClockTimer(); /* in case it was running already */
13429     DisplayBothClocks();
13430     if (CheckFlags()) return;
13431
13432     if (!appData.clockMode) return;
13433     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13434
13435     GetTimeMark(&tickStartTM);
13436     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13437       whiteTimeRemaining : blackTimeRemaining);
13438
13439    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13440     whiteNPS = blackNPS = -1; 
13441     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13442        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13443         whiteNPS = first.nps;
13444     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13445        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13446         blackNPS = first.nps;
13447     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13448         whiteNPS = second.nps;
13449     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13450         blackNPS = second.nps;
13451     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13452
13453     StartClockTimer(intendedTickLength);
13454 }
13455
13456 char *
13457 TimeString(ms)
13458      long ms;
13459 {
13460     long second, minute, hour, day;
13461     char *sign = "";
13462     static char buf[32];
13463     
13464     if (ms > 0 && ms <= 9900) {
13465       /* convert milliseconds to tenths, rounding up */
13466       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13467
13468       sprintf(buf, " %03.1f ", tenths/10.0);
13469       return buf;
13470     }
13471
13472     /* convert milliseconds to seconds, rounding up */
13473     /* use floating point to avoid strangeness of integer division
13474        with negative dividends on many machines */
13475     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13476
13477     if (second < 0) {
13478         sign = "-";
13479         second = -second;
13480     }
13481     
13482     day = second / (60 * 60 * 24);
13483     second = second % (60 * 60 * 24);
13484     hour = second / (60 * 60);
13485     second = second % (60 * 60);
13486     minute = second / 60;
13487     second = second % 60;
13488     
13489     if (day > 0)
13490       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13491               sign, day, hour, minute, second);
13492     else if (hour > 0)
13493       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13494     else
13495       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13496     
13497     return buf;
13498 }
13499
13500
13501 /*
13502  * This is necessary because some C libraries aren't ANSI C compliant yet.
13503  */
13504 char *
13505 StrStr(string, match)
13506      char *string, *match;
13507 {
13508     int i, length;
13509     
13510     length = strlen(match);
13511     
13512     for (i = strlen(string) - length; i >= 0; i--, string++)
13513       if (!strncmp(match, string, length))
13514         return string;
13515     
13516     return NULL;
13517 }
13518
13519 char *
13520 StrCaseStr(string, match)
13521      char *string, *match;
13522 {
13523     int i, j, length;
13524     
13525     length = strlen(match);
13526     
13527     for (i = strlen(string) - length; i >= 0; i--, string++) {
13528         for (j = 0; j < length; j++) {
13529             if (ToLower(match[j]) != ToLower(string[j]))
13530               break;
13531         }
13532         if (j == length) return string;
13533     }
13534
13535     return NULL;
13536 }
13537
13538 #ifndef _amigados
13539 int
13540 StrCaseCmp(s1, s2)
13541      char *s1, *s2;
13542 {
13543     char c1, c2;
13544     
13545     for (;;) {
13546         c1 = ToLower(*s1++);
13547         c2 = ToLower(*s2++);
13548         if (c1 > c2) return 1;
13549         if (c1 < c2) return -1;
13550         if (c1 == NULLCHAR) return 0;
13551     }
13552 }
13553
13554
13555 int
13556 ToLower(c)
13557      int c;
13558 {
13559     return isupper(c) ? tolower(c) : c;
13560 }
13561
13562
13563 int
13564 ToUpper(c)
13565      int c;
13566 {
13567     return islower(c) ? toupper(c) : c;
13568 }
13569 #endif /* !_amigados    */
13570
13571 char *
13572 StrSave(s)
13573      char *s;
13574 {
13575     char *ret;
13576
13577     if ((ret = (char *) malloc(strlen(s) + 1))) {
13578         strcpy(ret, s);
13579     }
13580     return ret;
13581 }
13582
13583 char *
13584 StrSavePtr(s, savePtr)
13585      char *s, **savePtr;
13586 {
13587     if (*savePtr) {
13588         free(*savePtr);
13589     }
13590     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13591         strcpy(*savePtr, s);
13592     }
13593     return(*savePtr);
13594 }
13595
13596 char *
13597 PGNDate()
13598 {
13599     time_t clock;
13600     struct tm *tm;
13601     char buf[MSG_SIZ];
13602
13603     clock = time((time_t *)NULL);
13604     tm = localtime(&clock);
13605     sprintf(buf, "%04d.%02d.%02d",
13606             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13607     return StrSave(buf);
13608 }
13609
13610
13611 char *
13612 PositionToFEN(move, overrideCastling)
13613      int move;
13614      char *overrideCastling;
13615 {
13616     int i, j, fromX, fromY, toX, toY;
13617     int whiteToPlay;
13618     char buf[128];
13619     char *p, *q;
13620     int emptycount;
13621     ChessSquare piece;
13622
13623     whiteToPlay = (gameMode == EditPosition) ?
13624       !blackPlaysFirst : (move % 2 == 0);
13625     p = buf;
13626
13627     /* Piece placement data */
13628     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13629         emptycount = 0;
13630         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13631             if (boards[move][i][j] == EmptySquare) {
13632                 emptycount++;
13633             } else { ChessSquare piece = boards[move][i][j];
13634                 if (emptycount > 0) {
13635                     if(emptycount<10) /* [HGM] can be >= 10 */
13636                         *p++ = '0' + emptycount;
13637                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13638                     emptycount = 0;
13639                 }
13640                 if(PieceToChar(piece) == '+') {
13641                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13642                     *p++ = '+';
13643                     piece = (ChessSquare)(DEMOTED piece);
13644                 } 
13645                 *p++ = PieceToChar(piece);
13646                 if(p[-1] == '~') {
13647                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13648                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13649                     *p++ = '~';
13650                 }
13651             }
13652         }
13653         if (emptycount > 0) {
13654             if(emptycount<10) /* [HGM] can be >= 10 */
13655                 *p++ = '0' + emptycount;
13656             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13657             emptycount = 0;
13658         }
13659         *p++ = '/';
13660     }
13661     *(p - 1) = ' ';
13662
13663     /* [HGM] print Crazyhouse or Shogi holdings */
13664     if( gameInfo.holdingsWidth ) {
13665         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13666         q = p;
13667         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13668             piece = boards[move][i][BOARD_WIDTH-1];
13669             if( piece != EmptySquare )
13670               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13671                   *p++ = PieceToChar(piece);
13672         }
13673         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13674             piece = boards[move][BOARD_HEIGHT-i-1][0];
13675             if( piece != EmptySquare )
13676               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13677                   *p++ = PieceToChar(piece);
13678         }
13679
13680         if( q == p ) *p++ = '-';
13681         *p++ = ']';
13682         *p++ = ' ';
13683     }
13684
13685     /* Active color */
13686     *p++ = whiteToPlay ? 'w' : 'b';
13687     *p++ = ' ';
13688
13689   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13690     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13691   } else {
13692   if(nrCastlingRights) {
13693      q = p;
13694      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13695        /* [HGM] write directly from rights */
13696            if(boards[move][CASTLING][2] != NoRights &&
13697               boards[move][CASTLING][0] != NoRights   )
13698                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13699            if(boards[move][CASTLING][2] != NoRights &&
13700               boards[move][CASTLING][1] != NoRights   )
13701                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13702            if(boards[move][CASTLING][5] != NoRights &&
13703               boards[move][CASTLING][3] != NoRights   )
13704                 *p++ = boards[move][CASTLING][3] + AAA;
13705            if(boards[move][CASTLING][5] != NoRights &&
13706               boards[move][CASTLING][4] != NoRights   )
13707                 *p++ = boards[move][CASTLING][4] + AAA;
13708      } else {
13709
13710         /* [HGM] write true castling rights */
13711         if( nrCastlingRights == 6 ) {
13712             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13713                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13714             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13715                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13716             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13717                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13718             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13719                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13720         }
13721      }
13722      if (q == p) *p++ = '-'; /* No castling rights */
13723      *p++ = ' ';
13724   }
13725
13726   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13727      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13728     /* En passant target square */
13729     if (move > backwardMostMove) {
13730         fromX = moveList[move - 1][0] - AAA;
13731         fromY = moveList[move - 1][1] - ONE;
13732         toX = moveList[move - 1][2] - AAA;
13733         toY = moveList[move - 1][3] - ONE;
13734         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13735             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13736             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13737             fromX == toX) {
13738             /* 2-square pawn move just happened */
13739             *p++ = toX + AAA;
13740             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13741         } else {
13742             *p++ = '-';
13743         }
13744     } else if(move == backwardMostMove) {
13745         // [HGM] perhaps we should always do it like this, and forget the above?
13746         if((signed char)boards[move][EP_STATUS] >= 0) {
13747             *p++ = boards[move][EP_STATUS] + AAA;
13748             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13749         } else {
13750             *p++ = '-';
13751         }
13752     } else {
13753         *p++ = '-';
13754     }
13755     *p++ = ' ';
13756   }
13757   }
13758
13759     /* [HGM] find reversible plies */
13760     {   int i = 0, j=move;
13761
13762         if (appData.debugMode) { int k;
13763             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13764             for(k=backwardMostMove; k<=forwardMostMove; k++)
13765                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13766
13767         }
13768
13769         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13770         if( j == backwardMostMove ) i += initialRulePlies;
13771         sprintf(p, "%d ", i);
13772         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13773     }
13774     /* Fullmove number */
13775     sprintf(p, "%d", (move / 2) + 1);
13776     
13777     return StrSave(buf);
13778 }
13779
13780 Boolean
13781 ParseFEN(board, blackPlaysFirst, fen)
13782     Board board;
13783      int *blackPlaysFirst;
13784      char *fen;
13785 {
13786     int i, j;
13787     char *p;
13788     int emptycount;
13789     ChessSquare piece;
13790
13791     p = fen;
13792
13793     /* [HGM] by default clear Crazyhouse holdings, if present */
13794     if(gameInfo.holdingsWidth) {
13795        for(i=0; i<BOARD_HEIGHT; i++) {
13796            board[i][0]             = EmptySquare; /* black holdings */
13797            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13798            board[i][1]             = (ChessSquare) 0; /* black counts */
13799            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13800        }
13801     }
13802
13803     /* Piece placement data */
13804     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13805         j = 0;
13806         for (;;) {
13807             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13808                 if (*p == '/') p++;
13809                 emptycount = gameInfo.boardWidth - j;
13810                 while (emptycount--)
13811                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13812                 break;
13813 #if(BOARD_FILES >= 10)
13814             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13815                 p++; emptycount=10;
13816                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13817                 while (emptycount--)
13818                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13819 #endif
13820             } else if (isdigit(*p)) {
13821                 emptycount = *p++ - '0';
13822                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13823                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13824                 while (emptycount--)
13825                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13826             } else if (*p == '+' || isalpha(*p)) {
13827                 if (j >= gameInfo.boardWidth) return FALSE;
13828                 if(*p=='+') {
13829                     piece = CharToPiece(*++p);
13830                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13831                     piece = (ChessSquare) (PROMOTED piece ); p++;
13832                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13833                 } else piece = CharToPiece(*p++);
13834
13835                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13836                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13837                     piece = (ChessSquare) (PROMOTED piece);
13838                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13839                     p++;
13840                 }
13841                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13842             } else {
13843                 return FALSE;
13844             }
13845         }
13846     }
13847     while (*p == '/' || *p == ' ') p++;
13848
13849     /* [HGM] look for Crazyhouse holdings here */
13850     while(*p==' ') p++;
13851     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13852         if(*p == '[') p++;
13853         if(*p == '-' ) *p++; /* empty holdings */ else {
13854             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13855             /* if we would allow FEN reading to set board size, we would   */
13856             /* have to add holdings and shift the board read so far here   */
13857             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13858                 *p++;
13859                 if((int) piece >= (int) BlackPawn ) {
13860                     i = (int)piece - (int)BlackPawn;
13861                     i = PieceToNumber((ChessSquare)i);
13862                     if( i >= gameInfo.holdingsSize ) return FALSE;
13863                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13864                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13865                 } else {
13866                     i = (int)piece - (int)WhitePawn;
13867                     i = PieceToNumber((ChessSquare)i);
13868                     if( i >= gameInfo.holdingsSize ) return FALSE;
13869                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13870                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13871                 }
13872             }
13873         }
13874         if(*p == ']') *p++;
13875     }
13876
13877     while(*p == ' ') p++;
13878
13879     /* Active color */
13880     switch (*p++) {
13881       case 'w':
13882         *blackPlaysFirst = FALSE;
13883         break;
13884       case 'b': 
13885         *blackPlaysFirst = TRUE;
13886         break;
13887       default:
13888         return FALSE;
13889     }
13890
13891     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13892     /* return the extra info in global variiables             */
13893
13894     /* set defaults in case FEN is incomplete */
13895     board[EP_STATUS] = EP_UNKNOWN;
13896     for(i=0; i<nrCastlingRights; i++ ) {
13897         board[CASTLING][i] =
13898             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13899     }   /* assume possible unless obviously impossible */
13900     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13901     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13902     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13903     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13904     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13905     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13906     FENrulePlies = 0;
13907
13908     while(*p==' ') p++;
13909     if(nrCastlingRights) {
13910       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13911           /* castling indicator present, so default becomes no castlings */
13912           for(i=0; i<nrCastlingRights; i++ ) {
13913                  board[CASTLING][i] = NoRights;
13914           }
13915       }
13916       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13917              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13918              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13919              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13920         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13921
13922         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13923             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13924             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13925         }
13926         switch(c) {
13927           case'K':
13928               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13929               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13930               board[CASTLING][2] = whiteKingFile;
13931               break;
13932           case'Q':
13933               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13934               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13935               board[CASTLING][2] = whiteKingFile;
13936               break;
13937           case'k':
13938               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13939               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13940               board[CASTLING][5] = blackKingFile;
13941               break;
13942           case'q':
13943               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13944               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13945               board[CASTLING][5] = blackKingFile;
13946           case '-':
13947               break;
13948           default: /* FRC castlings */
13949               if(c >= 'a') { /* black rights */
13950                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13951                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13952                   if(i == BOARD_RGHT) break;
13953                   board[CASTLING][5] = i;
13954                   c -= AAA;
13955                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13956                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13957                   if(c > i)
13958                       board[CASTLING][3] = c;
13959                   else
13960                       board[CASTLING][4] = c;
13961               } else { /* white rights */
13962                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13963                     if(board[0][i] == WhiteKing) break;
13964                   if(i == BOARD_RGHT) break;
13965                   board[CASTLING][2] = i;
13966                   c -= AAA - 'a' + 'A';
13967                   if(board[0][c] >= WhiteKing) break;
13968                   if(c > i)
13969                       board[CASTLING][0] = c;
13970                   else
13971                       board[CASTLING][1] = c;
13972               }
13973         }
13974       }
13975     if (appData.debugMode) {
13976         fprintf(debugFP, "FEN castling rights:");
13977         for(i=0; i<nrCastlingRights; i++)
13978         fprintf(debugFP, " %d", board[CASTLING][i]);
13979         fprintf(debugFP, "\n");
13980     }
13981
13982       while(*p==' ') p++;
13983     }
13984
13985     /* read e.p. field in games that know e.p. capture */
13986     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13987        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13988       if(*p=='-') {
13989         p++; board[EP_STATUS] = EP_NONE;
13990       } else {
13991          char c = *p++ - AAA;
13992
13993          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13994          if(*p >= '0' && *p <='9') *p++;
13995          board[EP_STATUS] = c;
13996       }
13997     }
13998
13999
14000     if(sscanf(p, "%d", &i) == 1) {
14001         FENrulePlies = i; /* 50-move ply counter */
14002         /* (The move number is still ignored)    */
14003     }
14004
14005     return TRUE;
14006 }
14007       
14008 void
14009 EditPositionPasteFEN(char *fen)
14010 {
14011   if (fen != NULL) {
14012     Board initial_position;
14013
14014     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14015       DisplayError(_("Bad FEN position in clipboard"), 0);
14016       return ;
14017     } else {
14018       int savedBlackPlaysFirst = blackPlaysFirst;
14019       EditPositionEvent();
14020       blackPlaysFirst = savedBlackPlaysFirst;
14021       CopyBoard(boards[0], initial_position);
14022       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14023       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14024       DisplayBothClocks();
14025       DrawPosition(FALSE, boards[currentMove]);
14026     }
14027   }
14028 }
14029
14030 static char cseq[12] = "\\   ";
14031
14032 Boolean set_cont_sequence(char *new_seq)
14033 {
14034     int len;
14035     Boolean ret;
14036
14037     // handle bad attempts to set the sequence
14038         if (!new_seq)
14039                 return 0; // acceptable error - no debug
14040
14041     len = strlen(new_seq);
14042     ret = (len > 0) && (len < sizeof(cseq));
14043     if (ret)
14044         strcpy(cseq, new_seq);
14045     else if (appData.debugMode)
14046         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14047     return ret;
14048 }
14049
14050 /*
14051     reformat a source message so words don't cross the width boundary.  internal
14052     newlines are not removed.  returns the wrapped size (no null character unless
14053     included in source message).  If dest is NULL, only calculate the size required
14054     for the dest buffer.  lp argument indicats line position upon entry, and it's
14055     passed back upon exit.
14056 */
14057 int wrap(char *dest, char *src, int count, int width, int *lp)
14058 {
14059     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14060
14061     cseq_len = strlen(cseq);
14062     old_line = line = *lp;
14063     ansi = len = clen = 0;
14064
14065     for (i=0; i < count; i++)
14066     {
14067         if (src[i] == '\033')
14068             ansi = 1;
14069
14070         // if we hit the width, back up
14071         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14072         {
14073             // store i & len in case the word is too long
14074             old_i = i, old_len = len;
14075
14076             // find the end of the last word
14077             while (i && src[i] != ' ' && src[i] != '\n')
14078             {
14079                 i--;
14080                 len--;
14081             }
14082
14083             // word too long?  restore i & len before splitting it
14084             if ((old_i-i+clen) >= width)
14085             {
14086                 i = old_i;
14087                 len = old_len;
14088             }
14089
14090             // extra space?
14091             if (i && src[i-1] == ' ')
14092                 len--;
14093
14094             if (src[i] != ' ' && src[i] != '\n')
14095             {
14096                 i--;
14097                 if (len)
14098                     len--;
14099             }
14100
14101             // now append the newline and continuation sequence
14102             if (dest)
14103                 dest[len] = '\n';
14104             len++;
14105             if (dest)
14106                 strncpy(dest+len, cseq, cseq_len);
14107             len += cseq_len;
14108             line = cseq_len;
14109             clen = cseq_len;
14110             continue;
14111         }
14112
14113         if (dest)
14114             dest[len] = src[i];
14115         len++;
14116         if (!ansi)
14117             line++;
14118         if (src[i] == '\n')
14119             line = 0;
14120         if (src[i] == 'm')
14121             ansi = 0;
14122     }
14123     if (dest && appData.debugMode)
14124     {
14125         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14126             count, width, line, len, *lp);
14127         show_bytes(debugFP, src, count);
14128         fprintf(debugFP, "\ndest: ");
14129         show_bytes(debugFP, dest, len);
14130         fprintf(debugFP, "\n");
14131     }
14132     *lp = dest ? line : old_line;
14133
14134     return len;
14135 }
14136
14137 // [HGM] vari: routines for shelving variations
14138
14139 void 
14140 PushTail(int firstMove, int lastMove)
14141 {
14142         int i, j, nrMoves = lastMove - firstMove;
14143
14144         if(appData.icsActive) { // only in local mode
14145                 forwardMostMove = currentMove; // mimic old ICS behavior
14146                 return;
14147         }
14148         if(storedGames >= MAX_VARIATIONS-1) return;
14149
14150         // push current tail of game on stack
14151         savedResult[storedGames] = gameInfo.result;
14152         savedDetails[storedGames] = gameInfo.resultDetails;
14153         gameInfo.resultDetails = NULL;
14154         savedFirst[storedGames] = firstMove;
14155         savedLast [storedGames] = lastMove;
14156         savedFramePtr[storedGames] = framePtr;
14157         framePtr -= nrMoves; // reserve space for the boards
14158         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14159             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14160             for(j=0; j<MOVE_LEN; j++)
14161                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14162             for(j=0; j<2*MOVE_LEN; j++)
14163                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14164             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14165             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14166             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14167             pvInfoList[firstMove+i-1].depth = 0;
14168             commentList[framePtr+i] = commentList[firstMove+i];
14169             commentList[firstMove+i] = NULL;
14170         }
14171
14172         storedGames++;
14173         forwardMostMove = currentMove; // truncte game so we can start variation
14174         if(storedGames == 1) GreyRevert(FALSE);
14175 }
14176
14177 Boolean 
14178 PopTail()
14179 {
14180         int i, j, nrMoves;
14181
14182         if(appData.icsActive) return FALSE; // only in local mode
14183         if(!storedGames) return FALSE; // sanity
14184
14185         storedGames--;
14186         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14187         nrMoves = savedLast[storedGames] - currentMove;
14188         for(i=1; i<nrMoves; i++) { // copy last variation back
14189             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14190             for(j=0; j<MOVE_LEN; j++)
14191                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14192             for(j=0; j<2*MOVE_LEN; j++)
14193                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14194             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14195             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14196             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14197             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14198             commentList[currentMove+i] = commentList[framePtr+i];
14199             commentList[framePtr+i] = NULL;
14200         }
14201         framePtr = savedFramePtr[storedGames];
14202         gameInfo.result = savedResult[storedGames];
14203         if(gameInfo.resultDetails != NULL) {
14204             free(gameInfo.resultDetails);
14205       }
14206         gameInfo.resultDetails = savedDetails[storedGames];
14207         forwardMostMove = currentMove + nrMoves;
14208         if(storedGames == 0) GreyRevert(TRUE);
14209         return TRUE;
14210 }
14211
14212 void 
14213 CleanupTail()
14214 {       // remove all shelved variations
14215         int i;
14216         for(i=0; i<storedGames; i++) {
14217             if(savedDetails[i])
14218                 free(savedDetails[i]);
14219             savedDetails[i] = NULL;
14220         }
14221         for(i=framePtr; i<MAX_MOVES; i++) {
14222                 if(commentList[i]) free(commentList[i]);
14223                 commentList[i] = NULL;
14224         }
14225         framePtr = MAX_MOVES-1;
14226         storedGames = 0;
14227 }