added missing sounds files to be able to compile on windows
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char  initialRights[BOARD_FILES];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
449 int loadFlag = 0; 
450 int shuffleOpenings;
451 int mute; // mute all sounds
452
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
456 int storedGames = 0;
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
462
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((Boolean annotate));
465 void CleanupTail P((void));
466
467 ChessSquare  FIDEArray[2][BOARD_FILES] = {
468     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471         BlackKing, BlackBishop, BlackKnight, BlackRook }
472 };
473
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackKing, BlackKnight, BlackRook }
479 };
480
481 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484     { BlackRook, BlackMan, BlackBishop, BlackQueen,
485         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
486 };
487
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492         BlackKing, BlackBishop, BlackKnight, BlackRook }
493 };
494
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
500 };
501
502
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
509 };
510
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
516 };
517
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
520         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
522         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
523 };
524
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
527         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
529         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
530 };
531
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
534         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
536         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
537 };
538
539 #ifdef GOTHIC
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
542         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
544         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !GOTHIC
547 #define GothicArray CapablancaArray
548 #endif // !GOTHIC
549
550 #ifdef FALCON
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
553         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
555         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !FALCON
558 #define FalconArray CapablancaArray
559 #endif // !FALCON
560
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
567
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
574 };
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
578
579
580 Board initialPosition;
581
582
583 /* Convert str to a rating. Checks for special cases of "----",
584
585    "++++", etc. Also strips ()'s */
586 int
587 string_to_rating(str)
588   char *str;
589 {
590   while(*str && !isdigit(*str)) ++str;
591   if (!*str)
592     return 0;   /* One of the special "no rating" cases */
593   else
594     return atoi(str);
595 }
596
597 void
598 ClearProgramStats()
599 {
600     /* Init programStats */
601     programStats.movelist[0] = 0;
602     programStats.depth = 0;
603     programStats.nr_moves = 0;
604     programStats.moves_left = 0;
605     programStats.nodes = 0;
606     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
607     programStats.score = 0;
608     programStats.got_only_move = 0;
609     programStats.got_fail = 0;
610     programStats.line_is_book = 0;
611 }
612
613 void
614 InitBackEnd1()
615 {
616     int matched, min, sec;
617
618     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
619
620     GetTimeMark(&programStartTime);
621     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
622
623     ClearProgramStats();
624     programStats.ok_to_send = 1;
625     programStats.seen_stat = 0;
626
627     /*
628      * Initialize game list
629      */
630     ListNew(&gameList);
631
632
633     /*
634      * Internet chess server status
635      */
636     if (appData.icsActive) {
637         appData.matchMode = FALSE;
638         appData.matchGames = 0;
639 #if ZIPPY       
640         appData.noChessProgram = !appData.zippyPlay;
641 #else
642         appData.zippyPlay = FALSE;
643         appData.zippyTalk = FALSE;
644         appData.noChessProgram = TRUE;
645 #endif
646         if (*appData.icsHelper != NULLCHAR) {
647             appData.useTelnet = TRUE;
648             appData.telnetProgram = appData.icsHelper;
649         }
650     } else {
651         appData.zippyTalk = appData.zippyPlay = FALSE;
652     }
653
654     /* [AS] Initialize pv info list [HGM] and game state */
655     {
656         int i, j;
657
658         for( i=0; i<=framePtr; i++ ) {
659             pvInfoList[i].depth = -1;
660             boards[i][EP_STATUS] = EP_NONE;
661             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
662         }
663     }
664
665     /*
666      * Parse timeControl resource
667      */
668     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669                           appData.movesPerSession)) {
670         char buf[MSG_SIZ];
671         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672         DisplayFatalError(buf, 0, 2);
673     }
674
675     /*
676      * Parse searchTime resource
677      */
678     if (*appData.searchTime != NULLCHAR) {
679         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
680         if (matched == 1) {
681             searchTime = min * 60;
682         } else if (matched == 2) {
683             searchTime = min * 60 + sec;
684         } else {
685             char buf[MSG_SIZ];
686             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687             DisplayFatalError(buf, 0, 2);
688         }
689     }
690
691     /* [AS] Adjudication threshold */
692     adjudicateLossThreshold = appData.adjudicateLossThreshold;
693     
694     first.which = "first";
695     second.which = "second";
696     first.maybeThinking = second.maybeThinking = FALSE;
697     first.pr = second.pr = NoProc;
698     first.isr = second.isr = NULL;
699     first.sendTime = second.sendTime = 2;
700     first.sendDrawOffers = 1;
701     if (appData.firstPlaysBlack) {
702         first.twoMachinesColor = "black\n";
703         second.twoMachinesColor = "white\n";
704     } else {
705         first.twoMachinesColor = "white\n";
706         second.twoMachinesColor = "black\n";
707     }
708     first.program = appData.firstChessProgram;
709     second.program = appData.secondChessProgram;
710     first.host = appData.firstHost;
711     second.host = appData.secondHost;
712     first.dir = appData.firstDirectory;
713     second.dir = appData.secondDirectory;
714     first.other = &second;
715     second.other = &first;
716     first.initString = appData.initString;
717     second.initString = appData.secondInitString;
718     first.computerString = appData.firstComputerString;
719     second.computerString = appData.secondComputerString;
720     first.useSigint = second.useSigint = TRUE;
721     first.useSigterm = second.useSigterm = TRUE;
722     first.reuse = appData.reuseFirst;
723     second.reuse = appData.reuseSecond;
724     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
725     second.nps = appData.secondNPS;
726     first.useSetboard = second.useSetboard = FALSE;
727     first.useSAN = second.useSAN = FALSE;
728     first.usePing = second.usePing = FALSE;
729     first.lastPing = second.lastPing = 0;
730     first.lastPong = second.lastPong = 0;
731     first.usePlayother = second.usePlayother = FALSE;
732     first.useColors = second.useColors = TRUE;
733     first.useUsermove = second.useUsermove = FALSE;
734     first.sendICS = second.sendICS = FALSE;
735     first.sendName = second.sendName = appData.icsActive;
736     first.sdKludge = second.sdKludge = FALSE;
737     first.stKludge = second.stKludge = FALSE;
738     TidyProgramName(first.program, first.host, first.tidy);
739     TidyProgramName(second.program, second.host, second.tidy);
740     first.matchWins = second.matchWins = 0;
741     strcpy(first.variants, appData.variant);
742     strcpy(second.variants, appData.variant);
743     first.analysisSupport = second.analysisSupport = 2; /* detect */
744     first.analyzing = second.analyzing = FALSE;
745     first.initDone = second.initDone = FALSE;
746
747     /* New features added by Tord: */
748     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750     /* End of new features added by Tord. */
751     first.fenOverride  = appData.fenOverride1;
752     second.fenOverride = appData.fenOverride2;
753
754     /* [HGM] time odds: set factor for each machine */
755     first.timeOdds  = appData.firstTimeOdds;
756     second.timeOdds = appData.secondTimeOdds;
757     { int norm = 1;
758         if(appData.timeOddsMode) {
759             norm = first.timeOdds;
760             if(norm > second.timeOdds) norm = second.timeOdds;
761         }
762         first.timeOdds /= norm;
763         second.timeOdds /= norm;
764     }
765
766     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767     first.accumulateTC = appData.firstAccumulateTC;
768     second.accumulateTC = appData.secondAccumulateTC;
769     first.maxNrOfSessions = second.maxNrOfSessions = 1;
770
771     /* [HGM] debug */
772     first.debug = second.debug = FALSE;
773     first.supportsNPS = second.supportsNPS = UNKNOWN;
774
775     /* [HGM] options */
776     first.optionSettings  = appData.firstOptions;
777     second.optionSettings = appData.secondOptions;
778
779     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781     first.isUCI = appData.firstIsUCI; /* [AS] */
782     second.isUCI = appData.secondIsUCI; /* [AS] */
783     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
785
786     if (appData.firstProtocolVersion > PROTOVER ||
787         appData.firstProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.firstProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       first.protocolVersion = appData.firstProtocolVersion;
794     }
795
796     if (appData.secondProtocolVersion > PROTOVER ||
797         appData.secondProtocolVersion < 1) {
798       char buf[MSG_SIZ];
799       sprintf(buf, _("protocol version %d not supported"),
800               appData.secondProtocolVersion);
801       DisplayFatalError(buf, 0, 2);
802     } else {
803       second.protocolVersion = appData.secondProtocolVersion;
804     }
805
806     if (appData.icsActive) {
807         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
808 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810         appData.clockMode = FALSE;
811         first.sendTime = second.sendTime = 0;
812     }
813     
814 #if ZIPPY
815     /* Override some settings from environment variables, for backward
816        compatibility.  Unfortunately it's not feasible to have the env
817        vars just set defaults, at least in xboard.  Ugh.
818     */
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
820       ZippyInit();
821     }
822 #endif
823     
824     if (appData.noChessProgram) {
825         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826         sprintf(programVersion, "%s", PACKAGE_STRING);
827     } else {
828       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
831     }
832
833     if (!appData.icsActive) {
834       char buf[MSG_SIZ];
835       /* Check for variants that are supported only in ICS mode,
836          or not at all.  Some that are accepted here nevertheless
837          have bugs; see comments below.
838       */
839       VariantClass variant = StringToVariant(appData.variant);
840       switch (variant) {
841       case VariantBughouse:     /* need four players and two boards */
842       case VariantKriegspiel:   /* need to hide pieces and move details */
843       /* case VariantFischeRandom: (Fabien: moved below) */
844         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845         DisplayFatalError(buf, 0, 2);
846         return;
847
848       case VariantUnknown:
849       case VariantLoadable:
850       case Variant29:
851       case Variant30:
852       case Variant31:
853       case Variant32:
854       case Variant33:
855       case Variant34:
856       case Variant35:
857       case Variant36:
858       default:
859         sprintf(buf, _("Unknown variant name %s"), appData.variant);
860         DisplayFatalError(buf, 0, 2);
861         return;
862
863       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
864       case VariantFairy:      /* [HGM] TestLegality definitely off! */
865       case VariantGothic:     /* [HGM] should work */
866       case VariantCapablanca: /* [HGM] should work */
867       case VariantCourier:    /* [HGM] initial forced moves not implemented */
868       case VariantShogi:      /* [HGM] drops not tested for legality */
869       case VariantKnightmate: /* [HGM] should work */
870       case VariantCylinder:   /* [HGM] untested */
871       case VariantFalcon:     /* [HGM] untested */
872       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873                                  offboard interposition not understood */
874       case VariantNormal:     /* definitely works! */
875       case VariantWildCastle: /* pieces not automatically shuffled */
876       case VariantNoCastle:   /* pieces not automatically shuffled */
877       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878       case VariantLosers:     /* should work except for win condition,
879                                  and doesn't know captures are mandatory */
880       case VariantSuicide:    /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantGiveaway:   /* should work except for win condition,
883                                  and doesn't know captures are mandatory */
884       case VariantTwoKings:   /* should work */
885       case VariantAtomic:     /* should work except for win condition */
886       case Variant3Check:     /* should work except for win condition */
887       case VariantShatranj:   /* should work except for all win conditions */
888       case VariantBerolina:   /* might work if TestLegality is off */
889       case VariantCapaRandom: /* should work */
890       case VariantJanus:      /* should work */
891       case VariantSuper:      /* experimental */
892       case VariantGreat:      /* experimental, requires legality testing to be off */
893         break;
894       }
895     }
896
897     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
898     InitEngineUCI( installDir, &second );
899 }
900
901 int NextIntegerFromString( char ** str, long * value )
902 {
903     int result = -1;
904     char * s = *str;
905
906     while( *s == ' ' || *s == '\t' ) {
907         s++;
908     }
909
910     *value = 0;
911
912     if( *s >= '0' && *s <= '9' ) {
913         while( *s >= '0' && *s <= '9' ) {
914             *value = *value * 10 + (*s - '0');
915             s++;
916         }
917
918         result = 0;
919     }
920
921     *str = s;
922
923     return result;
924 }
925
926 int NextTimeControlFromString( char ** str, long * value )
927 {
928     long temp;
929     int result = NextIntegerFromString( str, &temp );
930
931     if( result == 0 ) {
932         *value = temp * 60; /* Minutes */
933         if( **str == ':' ) {
934             (*str)++;
935             result = NextIntegerFromString( str, &temp );
936             *value += temp; /* Seconds */
937         }
938     }
939
940     return result;
941 }
942
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
945     int result = -1; long temp, temp2;
946
947     if(**str != '+') return -1; // old params remain in force!
948     (*str)++;
949     if( NextTimeControlFromString( str, &temp ) ) return -1;
950
951     if(**str != '/') {
952         /* time only: incremental or sudden-death time control */
953         if(**str == '+') { /* increment follows; read it */
954             (*str)++;
955             if(result = NextIntegerFromString( str, &temp2)) return -1;
956             *inc = temp2 * 1000;
957         } else *inc = 0;
958         *moves = 0; *tc = temp * 1000; 
959         return 0;
960     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
961
962     (*str)++; /* classical time control */
963     result = NextTimeControlFromString( str, &temp2);
964     if(result == 0) {
965         *moves = temp/60;
966         *tc    = temp2 * 1000;
967         *inc   = 0;
968     }
969     return result;
970 }
971
972 int GetTimeQuota(int movenr)
973 {   /* [HGM] get time to add from the multi-session time-control string */
974     int moves=1; /* kludge to force reading of first session */
975     long time, increment;
976     char *s = fullTimeControlString;
977
978     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
979     do {
980         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982         if(movenr == -1) return time;    /* last move before new session     */
983         if(!moves) return increment;     /* current session is incremental   */
984         if(movenr >= 0) movenr -= moves; /* we already finished this session */
985     } while(movenr >= -1);               /* try again for next session       */
986
987     return 0; // no new time quota on this move
988 }
989
990 int
991 ParseTimeControl(tc, ti, mps)
992      char *tc;
993      int ti;
994      int mps;
995 {
996   long tc1;
997   long tc2;
998   char buf[MSG_SIZ];
999   
1000   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1001   if(ti > 0) {
1002     if(mps)
1003       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004     else sprintf(buf, "+%s+%d", tc, ti);
1005   } else {
1006     if(mps)
1007              sprintf(buf, "+%d/%s", mps, tc);
1008     else sprintf(buf, "+%s", tc);
1009   }
1010   fullTimeControlString = StrSave(buf);
1011   
1012   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1013     return FALSE;
1014   }
1015   
1016   if( *tc == '/' ) {
1017     /* Parse second time control */
1018     tc++;
1019     
1020     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1021       return FALSE;
1022     }
1023     
1024     if( tc2 == 0 ) {
1025       return FALSE;
1026     }
1027     
1028     timeControl_2 = tc2 * 1000;
1029   }
1030   else {
1031     timeControl_2 = 0;
1032   }
1033   
1034   if( tc1 == 0 ) {
1035     return FALSE;
1036   }
1037   
1038   timeControl = tc1 * 1000;
1039   
1040   if (ti >= 0) {
1041     timeIncrement = ti * 1000;  /* convert to ms */
1042     movesPerSession = 0;
1043   } else {
1044     timeIncrement = 0;
1045     movesPerSession = mps;
1046   }
1047   return TRUE;
1048 }
1049
1050 void
1051 InitBackEnd2()
1052 {
1053     if (appData.debugMode) {
1054         fprintf(debugFP, "%s\n", programVersion);
1055     }
1056
1057     set_cont_sequence(appData.wrapContSeq);
1058     if (appData.matchGames > 0) {
1059         appData.matchMode = TRUE;
1060     } else if (appData.matchMode) {
1061         appData.matchGames = 1;
1062     }
1063     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064         appData.matchGames = appData.sameColorGames;
1065     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1068     }
1069     Reset(TRUE, FALSE);
1070     if (appData.noChessProgram || first.protocolVersion == 1) {
1071       InitBackEnd3();
1072     } else {
1073       /* kludge: allow timeout for initial "feature" commands */
1074       FreezeUI();
1075       DisplayMessage("", _("Starting chess program"));
1076       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1077     }
1078 }
1079
1080 void
1081 InitBackEnd3 P((void))
1082 {
1083     GameMode initialMode;
1084     char buf[MSG_SIZ];
1085     int err;
1086
1087     InitChessProgram(&first, startedFromSetupPosition);
1088
1089
1090     if (appData.icsActive) {
1091 #ifdef WIN32
1092         /* [DM] Make a console window if needed [HGM] merged ifs */
1093         ConsoleCreate(); 
1094 #endif
1095         err = establish();
1096         if (err != 0) {
1097             if (*appData.icsCommPort != NULLCHAR) {
1098                 sprintf(buf, _("Could not open comm port %s"),  
1099                         appData.icsCommPort);
1100             } else {
1101                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1102                         appData.icsHost, appData.icsPort);
1103             }
1104             DisplayFatalError(buf, err, 1);
1105             return;
1106         }
1107         SetICSMode();
1108         telnetISR =
1109           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1110         fromUserISR =
1111           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112     } else if (appData.noChessProgram) {
1113         SetNCPMode();
1114     } else {
1115         SetGNUMode();
1116     }
1117
1118     if (*appData.cmailGameName != NULLCHAR) {
1119         SetCmailMode();
1120         OpenLoopback(&cmailPR);
1121         cmailISR =
1122           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1123     }
1124     
1125     ThawUI();
1126     DisplayMessage("", "");
1127     if (StrCaseCmp(appData.initialMode, "") == 0) {
1128       initialMode = BeginningOfGame;
1129     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130       initialMode = TwoMachinesPlay;
1131     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132       initialMode = AnalyzeFile; 
1133     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134       initialMode = AnalyzeMode;
1135     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136       initialMode = MachinePlaysWhite;
1137     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138       initialMode = MachinePlaysBlack;
1139     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140       initialMode = EditGame;
1141     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142       initialMode = EditPosition;
1143     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144       initialMode = Training;
1145     } else {
1146       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147       DisplayFatalError(buf, 0, 2);
1148       return;
1149     }
1150
1151     if (appData.matchMode) {
1152         /* Set up machine vs. machine match */
1153         if (appData.noChessProgram) {
1154             DisplayFatalError(_("Can't have a match with no chess programs"),
1155                               0, 2);
1156             return;
1157         }
1158         matchMode = TRUE;
1159         matchGame = 1;
1160         if (*appData.loadGameFile != NULLCHAR) {
1161             int index = appData.loadGameIndex; // [HGM] autoinc
1162             if(index<0) lastIndex = index = 1;
1163             if (!LoadGameFromFile(appData.loadGameFile,
1164                                   index,
1165                                   appData.loadGameFile, FALSE)) {
1166                 DisplayFatalError(_("Bad game file"), 0, 1);
1167                 return;
1168             }
1169         } else if (*appData.loadPositionFile != NULLCHAR) {
1170             int index = appData.loadPositionIndex; // [HGM] autoinc
1171             if(index<0) lastIndex = index = 1;
1172             if (!LoadPositionFromFile(appData.loadPositionFile,
1173                                       index,
1174                                       appData.loadPositionFile)) {
1175                 DisplayFatalError(_("Bad position file"), 0, 1);
1176                 return;
1177             }
1178         }
1179         TwoMachinesEvent();
1180     } else if (*appData.cmailGameName != NULLCHAR) {
1181         /* Set up cmail mode */
1182         ReloadCmailMsgEvent(TRUE);
1183     } else {
1184         /* Set up other modes */
1185         if (initialMode == AnalyzeFile) {
1186           if (*appData.loadGameFile == NULLCHAR) {
1187             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1188             return;
1189           }
1190         }
1191         if (*appData.loadGameFile != NULLCHAR) {
1192             (void) LoadGameFromFile(appData.loadGameFile,
1193                                     appData.loadGameIndex,
1194                                     appData.loadGameFile, TRUE);
1195         } else if (*appData.loadPositionFile != NULLCHAR) {
1196             (void) LoadPositionFromFile(appData.loadPositionFile,
1197                                         appData.loadPositionIndex,
1198                                         appData.loadPositionFile);
1199             /* [HGM] try to make self-starting even after FEN load */
1200             /* to allow automatic setup of fairy variants with wtm */
1201             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202                 gameMode = BeginningOfGame;
1203                 setboardSpoiledMachineBlack = 1;
1204             }
1205             /* [HGM] loadPos: make that every new game uses the setup */
1206             /* from file as long as we do not switch variant          */
1207             if(!blackPlaysFirst) {
1208                 startedFromPositionFile = TRUE;
1209                 CopyBoard(filePosition, boards[0]);
1210             }
1211         }
1212         if (initialMode == AnalyzeMode) {
1213           if (appData.noChessProgram) {
1214             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1215             return;
1216           }
1217           if (appData.icsActive) {
1218             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1219             return;
1220           }
1221           AnalyzeModeEvent();
1222         } else if (initialMode == AnalyzeFile) {
1223           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224           ShowThinkingEvent();
1225           AnalyzeFileEvent();
1226           AnalysisPeriodicEvent(1);
1227         } else if (initialMode == MachinePlaysWhite) {
1228           if (appData.noChessProgram) {
1229             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1230                               0, 2);
1231             return;
1232           }
1233           if (appData.icsActive) {
1234             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1235                               0, 2);
1236             return;
1237           }
1238           MachineWhiteEvent();
1239         } else if (initialMode == MachinePlaysBlack) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           MachineBlackEvent();
1251         } else if (initialMode == TwoMachinesPlay) {
1252           if (appData.noChessProgram) {
1253             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1254                               0, 2);
1255             return;
1256           }
1257           if (appData.icsActive) {
1258             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1259                               0, 2);
1260             return;
1261           }
1262           TwoMachinesEvent();
1263         } else if (initialMode == EditGame) {
1264           EditGameEvent();
1265         } else if (initialMode == EditPosition) {
1266           EditPositionEvent();
1267         } else if (initialMode == Training) {
1268           if (*appData.loadGameFile == NULLCHAR) {
1269             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1270             return;
1271           }
1272           TrainingEvent();
1273         }
1274     }
1275 }
1276
1277 /*
1278  * Establish will establish a contact to a remote host.port.
1279  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280  *  used to talk to the host.
1281  * Returns 0 if okay, error code if not.
1282  */
1283 int
1284 establish()
1285 {
1286     char buf[MSG_SIZ];
1287
1288     if (*appData.icsCommPort != NULLCHAR) {
1289         /* Talk to the host through a serial comm port */
1290         return OpenCommPort(appData.icsCommPort, &icsPR);
1291
1292     } else if (*appData.gateway != NULLCHAR) {
1293         if (*appData.remoteShell == NULLCHAR) {
1294             /* Use the rcmd protocol to run telnet program on a gateway host */
1295             snprintf(buf, sizeof(buf), "%s %s %s",
1296                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1297             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1298
1299         } else {
1300             /* Use the rsh program to run telnet program on a gateway host */
1301             if (*appData.remoteUser == NULLCHAR) {
1302                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303                         appData.gateway, appData.telnetProgram,
1304                         appData.icsHost, appData.icsPort);
1305             } else {
1306                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307                         appData.remoteShell, appData.gateway, 
1308                         appData.remoteUser, appData.telnetProgram,
1309                         appData.icsHost, appData.icsPort);
1310             }
1311             return StartChildProcess(buf, "", &icsPR);
1312
1313         }
1314     } else if (appData.useTelnet) {
1315         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1316
1317     } else {
1318         /* TCP socket interface differs somewhat between
1319            Unix and NT; handle details in the front end.
1320            */
1321         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1322     }
1323 }
1324
1325 void
1326 show_bytes(fp, buf, count)
1327      FILE *fp;
1328      char *buf;
1329      int count;
1330 {
1331     while (count--) {
1332         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333             fprintf(fp, "\\%03o", *buf & 0xff);
1334         } else {
1335             putc(*buf, fp);
1336         }
1337         buf++;
1338     }
1339     fflush(fp);
1340 }
1341
1342 /* Returns an errno value */
1343 int
1344 OutputMaybeTelnet(pr, message, count, outError)
1345      ProcRef pr;
1346      char *message;
1347      int count;
1348      int *outError;
1349 {
1350     char buf[8192], *p, *q, *buflim;
1351     int left, newcount, outcount;
1352
1353     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354         *appData.gateway != NULLCHAR) {
1355         if (appData.debugMode) {
1356             fprintf(debugFP, ">ICS: ");
1357             show_bytes(debugFP, message, count);
1358             fprintf(debugFP, "\n");
1359         }
1360         return OutputToProcess(pr, message, count, outError);
1361     }
1362
1363     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1364     p = message;
1365     q = buf;
1366     left = count;
1367     newcount = 0;
1368     while (left) {
1369         if (q >= buflim) {
1370             if (appData.debugMode) {
1371                 fprintf(debugFP, ">ICS: ");
1372                 show_bytes(debugFP, buf, newcount);
1373                 fprintf(debugFP, "\n");
1374             }
1375             outcount = OutputToProcess(pr, buf, newcount, outError);
1376             if (outcount < newcount) return -1; /* to be sure */
1377             q = buf;
1378             newcount = 0;
1379         }
1380         if (*p == '\n') {
1381             *q++ = '\r';
1382             newcount++;
1383         } else if (((unsigned char) *p) == TN_IAC) {
1384             *q++ = (char) TN_IAC;
1385             newcount ++;
1386         }
1387         *q++ = *p++;
1388         newcount++;
1389         left--;
1390     }
1391     if (appData.debugMode) {
1392         fprintf(debugFP, ">ICS: ");
1393         show_bytes(debugFP, buf, newcount);
1394         fprintf(debugFP, "\n");
1395     }
1396     outcount = OutputToProcess(pr, buf, newcount, outError);
1397     if (outcount < newcount) return -1; /* to be sure */
1398     return count;
1399 }
1400
1401 void
1402 read_from_player(isr, closure, message, count, error)
1403      InputSourceRef isr;
1404      VOIDSTAR closure;
1405      char *message;
1406      int count;
1407      int error;
1408 {
1409     int outError, outCount;
1410     static int gotEof = 0;
1411
1412     /* Pass data read from player on to ICS */
1413     if (count > 0) {
1414         gotEof = 0;
1415         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416         if (outCount < count) {
1417             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1418         }
1419     } else if (count < 0) {
1420         RemoveInputSource(isr);
1421         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422     } else if (gotEof++ > 0) {
1423         RemoveInputSource(isr);
1424         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1425     }
1426 }
1427
1428 void
1429 KeepAlive()
1430 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431     SendToICS("date\n");
1432     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1433 }
1434
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1437 {
1438     char buffer[MSG_SIZ];
1439     va_list args;
1440
1441     va_start(args, format);
1442     vsnprintf(buffer, sizeof(buffer), format, args);
1443     buffer[sizeof(buffer)-1] = '\0';
1444     SendToICS(buffer);
1445     va_end(args);
1446 }
1447
1448 void
1449 SendToICS(s)
1450      char *s;
1451 {
1452     int count, outCount, outError;
1453
1454     if (icsPR == NULL) return;
1455
1456     count = strlen(s);
1457     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458     if (outCount < count) {
1459         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1460     }
1461 }
1462
1463 /* This is used for sending logon scripts to the ICS. Sending
1464    without a delay causes problems when using timestamp on ICC
1465    (at least on my machine). */
1466 void
1467 SendToICSDelayed(s,msdelay)
1468      char *s;
1469      long msdelay;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     if (appData.debugMode) {
1477         fprintf(debugFP, ">ICS: ");
1478         show_bytes(debugFP, s, count);
1479         fprintf(debugFP, "\n");
1480     }
1481     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1482                                       msdelay);
1483     if (outCount < count) {
1484         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1485     }
1486 }
1487
1488
1489 /* Remove all highlighting escape sequences in s
1490    Also deletes any suffix starting with '(' 
1491    */
1492 char *
1493 StripHighlightAndTitle(s)
1494      char *s;
1495 {
1496     static char retbuf[MSG_SIZ];
1497     char *p = retbuf;
1498
1499     while (*s != NULLCHAR) {
1500         while (*s == '\033') {
1501             while (*s != NULLCHAR && !isalpha(*s)) s++;
1502             if (*s != NULLCHAR) s++;
1503         }
1504         while (*s != NULLCHAR && *s != '\033') {
1505             if (*s == '(' || *s == '[') {
1506                 *p = NULLCHAR;
1507                 return retbuf;
1508             }
1509             *p++ = *s++;
1510         }
1511     }
1512     *p = NULLCHAR;
1513     return retbuf;
1514 }
1515
1516 /* Remove all highlighting escape sequences in s */
1517 char *
1518 StripHighlight(s)
1519      char *s;
1520 {
1521     static char retbuf[MSG_SIZ];
1522     char *p = retbuf;
1523
1524     while (*s != NULLCHAR) {
1525         while (*s == '\033') {
1526             while (*s != NULLCHAR && !isalpha(*s)) s++;
1527             if (*s != NULLCHAR) s++;
1528         }
1529         while (*s != NULLCHAR && *s != '\033') {
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 char *variantNames[] = VARIANT_NAMES;
1538 char *
1539 VariantName(v)
1540      VariantClass v;
1541 {
1542     return variantNames[v];
1543 }
1544
1545
1546 /* Identify a variant from the strings the chess servers use or the
1547    PGN Variant tag names we use. */
1548 VariantClass
1549 StringToVariant(e)
1550      char *e;
1551 {
1552     char *p;
1553     int wnum = -1;
1554     VariantClass v = VariantNormal;
1555     int i, found = FALSE;
1556     char buf[MSG_SIZ];
1557
1558     if (!e) return v;
1559
1560     /* [HGM] skip over optional board-size prefixes */
1561     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563         while( *e++ != '_');
1564     }
1565
1566     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1567         v = VariantNormal;
1568         found = TRUE;
1569     } else
1570     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571       if (StrCaseStr(e, variantNames[i])) {
1572         v = (VariantClass) i;
1573         found = TRUE;
1574         break;
1575       }
1576     }
1577
1578     if (!found) {
1579       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580           || StrCaseStr(e, "wild/fr") 
1581           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582         v = VariantFischeRandom;
1583       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584                  (i = 1, p = StrCaseStr(e, "w"))) {
1585         p += i;
1586         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1587         if (isdigit(*p)) {
1588           wnum = atoi(p);
1589         } else {
1590           wnum = -1;
1591         }
1592         switch (wnum) {
1593         case 0: /* FICS only, actually */
1594         case 1:
1595           /* Castling legal even if K starts on d-file */
1596           v = VariantWildCastle;
1597           break;
1598         case 2:
1599         case 3:
1600         case 4:
1601           /* Castling illegal even if K & R happen to start in
1602              normal positions. */
1603           v = VariantNoCastle;
1604           break;
1605         case 5:
1606         case 7:
1607         case 8:
1608         case 10:
1609         case 11:
1610         case 12:
1611         case 13:
1612         case 14:
1613         case 15:
1614         case 18:
1615         case 19:
1616           /* Castling legal iff K & R start in normal positions */
1617           v = VariantNormal;
1618           break;
1619         case 6:
1620         case 20:
1621         case 21:
1622           /* Special wilds for position setup; unclear what to do here */
1623           v = VariantLoadable;
1624           break;
1625         case 9:
1626           /* Bizarre ICC game */
1627           v = VariantTwoKings;
1628           break;
1629         case 16:
1630           v = VariantKriegspiel;
1631           break;
1632         case 17:
1633           v = VariantLosers;
1634           break;
1635         case 22:
1636           v = VariantFischeRandom;
1637           break;
1638         case 23:
1639           v = VariantCrazyhouse;
1640           break;
1641         case 24:
1642           v = VariantBughouse;
1643           break;
1644         case 25:
1645           v = Variant3Check;
1646           break;
1647         case 26:
1648           /* Not quite the same as FICS suicide! */
1649           v = VariantGiveaway;
1650           break;
1651         case 27:
1652           v = VariantAtomic;
1653           break;
1654         case 28:
1655           v = VariantShatranj;
1656           break;
1657
1658         /* Temporary names for future ICC types.  The name *will* change in 
1659            the next xboard/WinBoard release after ICC defines it. */
1660         case 29:
1661           v = Variant29;
1662           break;
1663         case 30:
1664           v = Variant30;
1665           break;
1666         case 31:
1667           v = Variant31;
1668           break;
1669         case 32:
1670           v = Variant32;
1671           break;
1672         case 33:
1673           v = Variant33;
1674           break;
1675         case 34:
1676           v = Variant34;
1677           break;
1678         case 35:
1679           v = Variant35;
1680           break;
1681         case 36:
1682           v = Variant36;
1683           break;
1684         case 37:
1685           v = VariantShogi;
1686           break;
1687         case 38:
1688           v = VariantXiangqi;
1689           break;
1690         case 39:
1691           v = VariantCourier;
1692           break;
1693         case 40:
1694           v = VariantGothic;
1695           break;
1696         case 41:
1697           v = VariantCapablanca;
1698           break;
1699         case 42:
1700           v = VariantKnightmate;
1701           break;
1702         case 43:
1703           v = VariantFairy;
1704           break;
1705         case 44:
1706           v = VariantCylinder;
1707           break;
1708         case 45:
1709           v = VariantFalcon;
1710           break;
1711         case 46:
1712           v = VariantCapaRandom;
1713           break;
1714         case 47:
1715           v = VariantBerolina;
1716           break;
1717         case 48:
1718           v = VariantJanus;
1719           break;
1720         case 49:
1721           v = VariantSuper;
1722           break;
1723         case 50:
1724           v = VariantGreat;
1725           break;
1726         case -1:
1727           /* Found "wild" or "w" in the string but no number;
1728              must assume it's normal chess. */
1729           v = VariantNormal;
1730           break;
1731         default:
1732           sprintf(buf, _("Unknown wild type %d"), wnum);
1733           DisplayError(buf, 0);
1734           v = VariantUnknown;
1735           break;
1736         }
1737       }
1738     }
1739     if (appData.debugMode) {
1740       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741               e, wnum, VariantName(v));
1742     }
1743     return v;
1744 }
1745
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1748
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750    advance *index beyond it, and set leftover_start to the new value of
1751    *index; else return FALSE.  If pattern contains the character '*', it
1752    matches any sequence of characters not containing '\r', '\n', or the
1753    character following the '*' (if any), and the matched sequence(s) are
1754    copied into star_match.
1755    */
1756 int
1757 looking_at(buf, index, pattern)
1758      char *buf;
1759      int *index;
1760      char *pattern;
1761 {
1762     char *bufp = &buf[*index], *patternp = pattern;
1763     int star_count = 0;
1764     char *matchp = star_match[0];
1765     
1766     for (;;) {
1767         if (*patternp == NULLCHAR) {
1768             *index = leftover_start = bufp - buf;
1769             *matchp = NULLCHAR;
1770             return TRUE;
1771         }
1772         if (*bufp == NULLCHAR) return FALSE;
1773         if (*patternp == '*') {
1774             if (*bufp == *(patternp + 1)) {
1775                 *matchp = NULLCHAR;
1776                 matchp = star_match[++star_count];
1777                 patternp += 2;
1778                 bufp++;
1779                 continue;
1780             } else if (*bufp == '\n' || *bufp == '\r') {
1781                 patternp++;
1782                 if (*patternp == NULLCHAR)
1783                   continue;
1784                 else
1785                   return FALSE;
1786             } else {
1787                 *matchp++ = *bufp++;
1788                 continue;
1789             }
1790         }
1791         if (*patternp != *bufp) return FALSE;
1792         patternp++;
1793         bufp++;
1794     }
1795 }
1796
1797 void
1798 SendToPlayer(data, length)
1799      char *data;
1800      int length;
1801 {
1802     int error, outCount;
1803     outCount = OutputToProcess(NoProc, data, length, &error);
1804     if (outCount < length) {
1805         DisplayFatalError(_("Error writing to display"), error, 1);
1806     }
1807 }
1808
1809 void
1810 PackHolding(packed, holding)
1811      char packed[];
1812      char *holding;
1813 {
1814     char *p = holding;
1815     char *q = packed;
1816     int runlength = 0;
1817     int curr = 9999;
1818     do {
1819         if (*p == curr) {
1820             runlength++;
1821         } else {
1822             switch (runlength) {
1823               case 0:
1824                 break;
1825               case 1:
1826                 *q++ = curr;
1827                 break;
1828               case 2:
1829                 *q++ = curr;
1830                 *q++ = curr;
1831                 break;
1832               default:
1833                 sprintf(q, "%d", runlength);
1834                 while (*q) q++;
1835                 *q++ = curr;
1836                 break;
1837             }
1838             runlength = 1;
1839             curr = *p;
1840         }
1841     } while (*p++);
1842     *q = NULLCHAR;
1843 }
1844
1845 /* Telnet protocol requests from the front end */
1846 void
1847 TelnetRequest(ddww, option)
1848      unsigned char ddww, option;
1849 {
1850     unsigned char msg[3];
1851     int outCount, outError;
1852
1853     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1854
1855     if (appData.debugMode) {
1856         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1857         switch (ddww) {
1858           case TN_DO:
1859             ddwwStr = "DO";
1860             break;
1861           case TN_DONT:
1862             ddwwStr = "DONT";
1863             break;
1864           case TN_WILL:
1865             ddwwStr = "WILL";
1866             break;
1867           case TN_WONT:
1868             ddwwStr = "WONT";
1869             break;
1870           default:
1871             ddwwStr = buf1;
1872             sprintf(buf1, "%d", ddww);
1873             break;
1874         }
1875         switch (option) {
1876           case TN_ECHO:
1877             optionStr = "ECHO";
1878             break;
1879           default:
1880             optionStr = buf2;
1881             sprintf(buf2, "%d", option);
1882             break;
1883         }
1884         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1885     }
1886     msg[0] = TN_IAC;
1887     msg[1] = ddww;
1888     msg[2] = option;
1889     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1890     if (outCount < 3) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895 void
1896 DoEcho()
1897 {
1898     if (!appData.icsActive) return;
1899     TelnetRequest(TN_DO, TN_ECHO);
1900 }
1901
1902 void
1903 DontEcho()
1904 {
1905     if (!appData.icsActive) return;
1906     TelnetRequest(TN_DONT, TN_ECHO);
1907 }
1908
1909 void
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1911 {
1912     /* put the holdings sent to us by the server on the board holdings area */
1913     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1914     char p;
1915     ChessSquare piece;
1916
1917     if(gameInfo.holdingsWidth < 2)  return;
1918     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919         return; // prevent overwriting by pre-board holdings
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948 }
1949
1950
1951 void
1952 VariantSwitch(Board board, VariantClass newVariant)
1953 {
1954    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1955    Board oldBoard;
1956
1957    startedFromPositionFile = FALSE;
1958    if(gameInfo.variant == newVariant) return;
1959
1960    /* [HGM] This routine is called each time an assignment is made to
1961     * gameInfo.variant during a game, to make sure the board sizes
1962     * are set to match the new variant. If that means adding or deleting
1963     * holdings, we shift the playing board accordingly
1964     * This kludge is needed because in ICS observe mode, we get boards
1965     * of an ongoing game without knowing the variant, and learn about the
1966     * latter only later. This can be because of the move list we requested,
1967     * in which case the game history is refilled from the beginning anyway,
1968     * but also when receiving holdings of a crazyhouse game. In the latter
1969     * case we want to add those holdings to the already received position.
1970     */
1971
1972    
1973    if (appData.debugMode) {
1974      fprintf(debugFP, "Switch board from %s to %s\n",
1975              VariantName(gameInfo.variant), VariantName(newVariant));
1976      setbuf(debugFP, NULL);
1977    }
1978    shuffleOpenings = 0;       /* [HGM] shuffle */
1979    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1980    switch(newVariant) 
1981      {
1982      case VariantShogi:
1983        newWidth = 9;  newHeight = 9;
1984        gameInfo.holdingsSize = 7;
1985      case VariantBughouse:
1986      case VariantCrazyhouse:
1987        newHoldingsWidth = 2; break;
1988      case VariantGreat:
1989        newWidth = 10;
1990      case VariantSuper:
1991        newHoldingsWidth = 2;
1992        gameInfo.holdingsSize = 8;
1993        break;
1994      case VariantGothic:
1995      case VariantCapablanca:
1996      case VariantCapaRandom:
1997        newWidth = 10;
1998      default:
1999        newHoldingsWidth = gameInfo.holdingsSize = 0;
2000      };
2001    
2002    if(newWidth  != gameInfo.boardWidth  ||
2003       newHeight != gameInfo.boardHeight ||
2004       newHoldingsWidth != gameInfo.holdingsWidth ) {
2005      
2006      /* shift position to new playing area, if needed */
2007      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008        for(i=0; i<BOARD_HEIGHT; i++) 
2009          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011              board[i][j];
2012        for(i=0; i<newHeight; i++) {
2013          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015        }
2016      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017        for(i=0; i<BOARD_HEIGHT; i++)
2018          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2020              board[i][j];
2021      }
2022      gameInfo.boardWidth  = newWidth;
2023      gameInfo.boardHeight = newHeight;
2024      gameInfo.holdingsWidth = newHoldingsWidth;
2025      gameInfo.variant = newVariant;
2026      InitDrawingSizes(-2, 0);
2027    } else gameInfo.variant = newVariant;
2028    CopyBoard(oldBoard, board);   // remember correctly formatted board
2029      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2030    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2031 }
2032
2033 static int loggedOn = FALSE;
2034
2035 /*-- Game start info cache: --*/
2036 int gs_gamenum;
2037 char gs_kind[MSG_SIZ];
2038 static char player1Name[128] = "";
2039 static char player2Name[128] = "";
2040 static char cont_seq[] = "\n\\   ";
2041 static int player1Rating = -1;
2042 static int player2Rating = -1;
2043 /*----------------------------*/
2044
2045 ColorClass curColor = ColorNormal;
2046 int suppressKibitz = 0;
2047
2048 void
2049 read_from_ics(isr, closure, data, count, error)
2050      InputSourceRef isr;
2051      VOIDSTAR closure;
2052      char *data;
2053      int count;
2054      int error;
2055 {
2056 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2057 #define STARTED_NONE 0
2058 #define STARTED_MOVES 1
2059 #define STARTED_BOARD 2
2060 #define STARTED_OBSERVE 3
2061 #define STARTED_HOLDINGS 4
2062 #define STARTED_CHATTER 5
2063 #define STARTED_COMMENT 6
2064 #define STARTED_MOVES_NOHIDE 7
2065     
2066     static int started = STARTED_NONE;
2067     static char parse[20000];
2068     static int parse_pos = 0;
2069     static char buf[BUF_SIZE + 1];
2070     static int firstTime = TRUE, intfSet = FALSE;
2071     static ColorClass prevColor = ColorNormal;
2072     static int savingComment = FALSE;
2073     static int cmatch = 0; // continuation sequence match
2074     char *bp;
2075     char str[500];
2076     int i, oldi;
2077     int buf_len;
2078     int next_out;
2079     int tkind;
2080     int backup;    /* [DM] For zippy color lines */
2081     char *p;
2082     char talker[MSG_SIZ]; // [HGM] chat
2083     int channel;
2084
2085     if (appData.debugMode) {
2086       if (!error) {
2087         fprintf(debugFP, "<ICS: ");
2088         show_bytes(debugFP, data, count);
2089         fprintf(debugFP, "\n");
2090       }
2091     }
2092
2093     if (appData.debugMode) { int f = forwardMostMove;
2094         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2095                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2096                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106     /* copy new characters into the buffer */
2107     bp = buf + leftover_len;
2108     buf_len=leftover_len;
2109     for (i=0; i<count; i++)
2110     {
2111         // ignore these
2112         if (data[i] == '\r')
2113             continue;
2114
2115         // join lines split by ICS?
2116         if (!appData.noJoin)
2117         {
2118             /*
2119                 Joining just consists of finding matches against the
2120                 continuation sequence, and discarding that sequence
2121                 if found instead of copying it.  So, until a match
2122                 fails, there's nothing to do since it might be the
2123                 complete sequence, and thus, something we don't want
2124                 copied.
2125             */
2126             if (data[i] == cont_seq[cmatch])
2127             {
2128                 cmatch++;
2129                 if (cmatch == strlen(cont_seq))
2130                 {
2131                     cmatch = 0; // complete match.  just reset the counter
2132
2133                     /*
2134                         it's possible for the ICS to not include the space
2135                         at the end of the last word, making our [correct]
2136                         join operation fuse two separate words.  the server
2137                         does this when the space occurs at the width setting.
2138                     */
2139                     if (!buf_len || buf[buf_len-1] != ' ')
2140                     {
2141                         *bp++ = ' ';
2142                         buf_len++;
2143                     }
2144                 }
2145                 continue;
2146             }
2147             else if (cmatch)
2148             {
2149                 /*
2150                     match failed, so we have to copy what matched before
2151                     falling through and copying this character.  In reality,
2152                     this will only ever be just the newline character, but
2153                     it doesn't hurt to be precise.
2154                 */
2155                 strncpy(bp, cont_seq, cmatch);
2156                 bp += cmatch;
2157                 buf_len += cmatch;
2158                 cmatch = 0;
2159             }
2160         }
2161
2162         // copy this char
2163         *bp++ = data[i];
2164         buf_len++;
2165     }
2166
2167         buf[buf_len] = NULLCHAR;
2168         next_out = leftover_len;
2169         leftover_start = 0;
2170         
2171         i = 0;
2172         while (i < buf_len) {
2173             /* Deal with part of the TELNET option negotiation
2174                protocol.  We refuse to do anything beyond the
2175                defaults, except that we allow the WILL ECHO option,
2176                which ICS uses to turn off password echoing when we are
2177                directly connected to it.  We reject this option
2178                if localLineEditing mode is on (always on in xboard)
2179                and we are talking to port 23, which might be a real
2180                telnet server that will try to keep WILL ECHO on permanently.
2181              */
2182             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2183                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2184                 unsigned char option;
2185                 oldi = i;
2186                 switch ((unsigned char) buf[++i]) {
2187                   case TN_WILL:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<WILL ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       case TN_ECHO:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "ECHO ");
2194                         /* Reply only if this is a change, according
2195                            to the protocol rules. */
2196                         if (remoteEchoOption) break;
2197                         if (appData.localLineEditing &&
2198                             atoi(appData.icsPort) == TN_PORT) {
2199                             TelnetRequest(TN_DONT, TN_ECHO);
2200                         } else {
2201                             EchoOff();
2202                             TelnetRequest(TN_DO, TN_ECHO);
2203                             remoteEchoOption = TRUE;
2204                         }
2205                         break;
2206                       default:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "%d ", option);
2209                         /* Whatever this is, we don't want it. */
2210                         TelnetRequest(TN_DONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_WONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<WONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       case TN_ECHO:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "ECHO ");
2221                         /* Reply only if this is a change, according
2222                            to the protocol rules. */
2223                         if (!remoteEchoOption) break;
2224                         EchoOn();
2225                         TelnetRequest(TN_DONT, TN_ECHO);
2226                         remoteEchoOption = FALSE;
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", (unsigned char) option);
2231                         /* Whatever this is, it must already be turned
2232                            off, because we never agree to turn on
2233                            anything non-default, so according to the
2234                            protocol rules, we don't reply. */
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DO:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DO ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         /* Whatever this is, we refuse to do it. */
2244                         if (appData.debugMode)
2245                           fprintf(debugFP, "%d ", option);
2246                         TelnetRequest(TN_WONT, option);
2247                         break;
2248                     }
2249                     break;
2250                   case TN_DONT:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<DONT ");
2253                     switch (option = (unsigned char) buf[++i]) {
2254                       default:
2255                         if (appData.debugMode)
2256                           fprintf(debugFP, "%d ", option);
2257                         /* Whatever this is, we are already not doing
2258                            it, because we never agree to do anything
2259                            non-default, so according to the protocol
2260                            rules, we don't reply. */
2261                         break;
2262                     }
2263                     break;
2264                   case TN_IAC:
2265                     if (appData.debugMode)
2266                       fprintf(debugFP, "\n<IAC ");
2267                     /* Doubled IAC; pass it through */
2268                     i--;
2269                     break;
2270                   default:
2271                     if (appData.debugMode)
2272                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2273                     /* Drop all other telnet commands on the floor */
2274                     break;
2275                 }
2276                 if (oldi > next_out)
2277                   SendToPlayer(&buf[next_out], oldi - next_out);
2278                 if (++i > next_out)
2279                   next_out = i;
2280                 continue;
2281             }
2282                 
2283             /* OK, this at least will *usually* work */
2284             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2285                 loggedOn = TRUE;
2286             }
2287             
2288             if (loggedOn && !intfSet) {
2289                 if (ics_type == ICS_ICC) {
2290                   sprintf(str,
2291                           "/set-quietly interface %s\n/set-quietly style 12\n",
2292                           programVersion);
2293                 } else if (ics_type == ICS_CHESSNET) {
2294                   sprintf(str, "/style 12\n");
2295                 } else {
2296                   strcpy(str, "alias $ @\n$set interface ");
2297                   strcat(str, programVersion);
2298                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2299 #ifdef WIN32
2300                   strcat(str, "$iset nohighlight 1\n");
2301 #endif
2302                   strcat(str, "$iset lock 1\n$style 12\n");
2303                 }
2304                 SendToICS(str);
2305                 NotifyFrontendLogin();
2306                 intfSet = TRUE;
2307             }
2308
2309             if (started == STARTED_COMMENT) {
2310                 /* Accumulate characters in comment */
2311                 parse[parse_pos++] = buf[i];
2312                 if (buf[i] == '\n') {
2313                     parse[parse_pos] = NULLCHAR;
2314                     if(chattingPartner>=0) {
2315                         char mess[MSG_SIZ];
2316                         sprintf(mess, "%s%s", talker, parse);
2317                         OutputChatMessage(chattingPartner, mess);
2318                         chattingPartner = -1;
2319                     } else
2320                     if(!suppressKibitz) // [HGM] kibitz
2321                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2322                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2323                         int nrDigit = 0, nrAlph = 0, i;
2324                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2325                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2326                         parse[parse_pos] = NULLCHAR;
2327                         // try to be smart: if it does not look like search info, it should go to
2328                         // ICS interaction window after all, not to engine-output window.
2329                         for(i=0; i<parse_pos; i++) { // count letters and digits
2330                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2331                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2332                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2333                         }
2334                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2335                             int depth=0; float score;
2336                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2337                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2338                                 pvInfoList[forwardMostMove-1].depth = depth;
2339                                 pvInfoList[forwardMostMove-1].score = 100*score;
2340                             }
2341                             OutputKibitz(suppressKibitz, parse);
2342                         } else {
2343                             char tmp[MSG_SIZ];
2344                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2345                             SendToPlayer(tmp, strlen(tmp));
2346                         }
2347                     }
2348                     started = STARTED_NONE;
2349                 } else {
2350                     /* Don't match patterns against characters in chatter */
2351                     i++;
2352                     continue;
2353                 }
2354             }
2355             if (started == STARTED_CHATTER) {
2356                 if (buf[i] != '\n') {
2357                     /* Don't match patterns against characters in chatter */
2358                     i++;
2359                     continue;
2360                 }
2361                 started = STARTED_NONE;
2362             }
2363
2364             /* Kludge to deal with rcmd protocol */
2365             if (firstTime && looking_at(buf, &i, "\001*")) {
2366                 DisplayFatalError(&buf[1], 0, 1);
2367                 continue;
2368             } else {
2369                 firstTime = FALSE;
2370             }
2371
2372             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2373                 ics_type = ICS_ICC;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2380                 ics_type = ICS_FICS;
2381                 ics_prefix = "$";
2382                 if (appData.debugMode)
2383                   fprintf(debugFP, "ics_type %d\n", ics_type);
2384                 continue;
2385             }
2386             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2387                 ics_type = ICS_CHESSNET;
2388                 ics_prefix = "/";
2389                 if (appData.debugMode)
2390                   fprintf(debugFP, "ics_type %d\n", ics_type);
2391                 continue;
2392             }
2393
2394             if (!loggedOn &&
2395                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2396                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2397                  looking_at(buf, &i, "will be \"*\""))) {
2398               strcpy(ics_handle, star_match[0]);
2399               continue;
2400             }
2401
2402             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2403               char buf[MSG_SIZ];
2404               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2405               DisplayIcsInteractionTitle(buf);
2406               have_set_title = TRUE;
2407             }
2408
2409             /* skip finger notes */
2410             if (started == STARTED_NONE &&
2411                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2412                  (buf[i] == '1' && buf[i+1] == '0')) &&
2413                 buf[i+2] == ':' && buf[i+3] == ' ') {
2414               started = STARTED_CHATTER;
2415               i += 3;
2416               continue;
2417             }
2418
2419             /* skip formula vars */
2420             if (started == STARTED_NONE &&
2421                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2422               started = STARTED_CHATTER;
2423               i += 3;
2424               continue;
2425             }
2426
2427             oldi = i;
2428             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2429             if (appData.autoKibitz && started == STARTED_NONE && 
2430                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2431                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2432                 if(looking_at(buf, &i, "* kibitzes: ") &&
2433                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2434                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2435                         suppressKibitz = TRUE;
2436                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2437                                 && (gameMode == IcsPlayingWhite)) ||
2438                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2439                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2440                             started = STARTED_CHATTER; // own kibitz we simply discard
2441                         else {
2442                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2443                             parse_pos = 0; parse[0] = NULLCHAR;
2444                             savingComment = TRUE;
2445                             suppressKibitz = gameMode != IcsObserving ? 2 :
2446                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2447                         } 
2448                         continue;
2449                 } else
2450                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2451                     started = STARTED_CHATTER;
2452                     suppressKibitz = TRUE;
2453                 }
2454             } // [HGM] kibitz: end of patch
2455
2456 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2457
2458             // [HGM] chat: intercept tells by users for which we have an open chat window
2459             channel = -1;
2460             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2461                                            looking_at(buf, &i, "* whispers:") ||
2462                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2463                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2464                 int p;
2465                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2466                 chattingPartner = -1;
2467
2468                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2469                 for(p=0; p<MAX_CHAT; p++) {
2470                     if(channel == atoi(chatPartner[p])) {
2471                     talker[0] = '['; strcat(talker, "]");
2472                     chattingPartner = p; break;
2473                     }
2474                 } else
2475                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2476                 for(p=0; p<MAX_CHAT; p++) {
2477                     if(!strcmp("WHISPER", chatPartner[p])) {
2478                         talker[0] = '['; strcat(talker, "]");
2479                         chattingPartner = p; break;
2480                     }
2481                 }
2482                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2483                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2484                     talker[0] = 0;
2485                     chattingPartner = p; break;
2486                 }
2487                 if(chattingPartner<0) i = oldi; else {
2488                     started = STARTED_COMMENT;
2489                     parse_pos = 0; parse[0] = NULLCHAR;
2490                     savingComment = TRUE;
2491                     suppressKibitz = TRUE;
2492                 }
2493             } // [HGM] chat: end of patch
2494
2495             if (appData.zippyTalk || appData.zippyPlay) {
2496                 /* [DM] Backup address for color zippy lines */
2497                 backup = i;
2498 #if ZIPPY
2499        #ifdef WIN32
2500                if (loggedOn == TRUE)
2501                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2502                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2503        #else
2504                 if (ZippyControl(buf, &i) ||
2505                     ZippyConverse(buf, &i) ||
2506                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2507                       loggedOn = TRUE;
2508                       if (!appData.colorize) continue;
2509                 }
2510        #endif
2511 #endif
2512             } // [DM] 'else { ' deleted
2513                 if (
2514                     /* Regular tells and says */
2515                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2516                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2517                     looking_at(buf, &i, "* says: ") ||
2518                     /* Don't color "message" or "messages" output */
2519                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2520                     looking_at(buf, &i, "*. * at *:*: ") ||
2521                     looking_at(buf, &i, "--* (*:*): ") ||
2522                     /* Message notifications (same color as tells) */
2523                     looking_at(buf, &i, "* has left a message ") ||
2524                     looking_at(buf, &i, "* just sent you a message:\n") ||
2525                     /* Whispers and kibitzes */
2526                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2527                     looking_at(buf, &i, "* kibitzes: ") ||
2528                     /* Channel tells */
2529                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2530
2531                   if (tkind == 1 && strchr(star_match[0], ':')) {
2532                       /* Avoid "tells you:" spoofs in channels */
2533                      tkind = 3;
2534                   }
2535                   if (star_match[0][0] == NULLCHAR ||
2536                       strchr(star_match[0], ' ') ||
2537                       (tkind == 3 && strchr(star_match[1], ' '))) {
2538                     /* Reject bogus matches */
2539                     i = oldi;
2540                   } else {
2541                     if (appData.colorize) {
2542                       if (oldi > next_out) {
2543                         SendToPlayer(&buf[next_out], oldi - next_out);
2544                         next_out = oldi;
2545                       }
2546                       switch (tkind) {
2547                       case 1:
2548                         Colorize(ColorTell, FALSE);
2549                         curColor = ColorTell;
2550                         break;
2551                       case 2:
2552                         Colorize(ColorKibitz, FALSE);
2553                         curColor = ColorKibitz;
2554                         break;
2555                       case 3:
2556                         p = strrchr(star_match[1], '(');
2557                         if (p == NULL) {
2558                           p = star_match[1];
2559                         } else {
2560                           p++;
2561                         }
2562                         if (atoi(p) == 1) {
2563                           Colorize(ColorChannel1, FALSE);
2564                           curColor = ColorChannel1;
2565                         } else {
2566                           Colorize(ColorChannel, FALSE);
2567                           curColor = ColorChannel;
2568                         }
2569                         break;
2570                       case 5:
2571                         curColor = ColorNormal;
2572                         break;
2573                       }
2574                     }
2575                     if (started == STARTED_NONE && appData.autoComment &&
2576                         (gameMode == IcsObserving ||
2577                          gameMode == IcsPlayingWhite ||
2578                          gameMode == IcsPlayingBlack)) {
2579                       parse_pos = i - oldi;
2580                       memcpy(parse, &buf[oldi], parse_pos);
2581                       parse[parse_pos] = NULLCHAR;
2582                       started = STARTED_COMMENT;
2583                       savingComment = TRUE;
2584                     } else {
2585                       started = STARTED_CHATTER;
2586                       savingComment = FALSE;
2587                     }
2588                     loggedOn = TRUE;
2589                     continue;
2590                   }
2591                 }
2592
2593                 if (looking_at(buf, &i, "* s-shouts: ") ||
2594                     looking_at(buf, &i, "* c-shouts: ")) {
2595                     if (appData.colorize) {
2596                         if (oldi > next_out) {
2597                             SendToPlayer(&buf[next_out], oldi - next_out);
2598                             next_out = oldi;
2599                         }
2600                         Colorize(ColorSShout, FALSE);
2601                         curColor = ColorSShout;
2602                     }
2603                     loggedOn = TRUE;
2604                     started = STARTED_CHATTER;
2605                     continue;
2606                 }
2607
2608                 if (looking_at(buf, &i, "--->")) {
2609                     loggedOn = TRUE;
2610                     continue;
2611                 }
2612
2613                 if (looking_at(buf, &i, "* shouts: ") ||
2614                     looking_at(buf, &i, "--> ")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorShout, FALSE);
2621                         curColor = ColorShout;
2622                     }
2623                     loggedOn = TRUE;
2624                     started = STARTED_CHATTER;
2625                     continue;
2626                 }
2627
2628                 if (looking_at( buf, &i, "Challenge:")) {
2629                     if (appData.colorize) {
2630                         if (oldi > next_out) {
2631                             SendToPlayer(&buf[next_out], oldi - next_out);
2632                             next_out = oldi;
2633                         }
2634                         Colorize(ColorChallenge, FALSE);
2635                         curColor = ColorChallenge;
2636                     }
2637                     loggedOn = TRUE;
2638                     continue;
2639                 }
2640
2641                 if (looking_at(buf, &i, "* offers you") ||
2642                     looking_at(buf, &i, "* offers to be") ||
2643                     looking_at(buf, &i, "* would like to") ||
2644                     looking_at(buf, &i, "* requests to") ||
2645                     looking_at(buf, &i, "Your opponent offers") ||
2646                     looking_at(buf, &i, "Your opponent requests")) {
2647
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorRequest, FALSE);
2654                         curColor = ColorRequest;
2655                     }
2656                     continue;
2657                 }
2658
2659                 if (looking_at(buf, &i, "* (*) seeking")) {
2660                     if (appData.colorize) {
2661                         if (oldi > next_out) {
2662                             SendToPlayer(&buf[next_out], oldi - next_out);
2663                             next_out = oldi;
2664                         }
2665                         Colorize(ColorSeek, FALSE);
2666                         curColor = ColorSeek;
2667                     }
2668                     continue;
2669             }
2670
2671             if (looking_at(buf, &i, "\\   ")) {
2672                 if (prevColor != ColorNormal) {
2673                     if (oldi > next_out) {
2674                         SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = oldi;
2676                     }
2677                     Colorize(prevColor, TRUE);
2678                     curColor = prevColor;
2679                 }
2680                 if (savingComment) {
2681                     parse_pos = i - oldi;
2682                     memcpy(parse, &buf[oldi], parse_pos);
2683                     parse[parse_pos] = NULLCHAR;
2684                     started = STARTED_COMMENT;
2685                 } else {
2686                     started = STARTED_CHATTER;
2687                 }
2688                 continue;
2689             }
2690
2691             if (looking_at(buf, &i, "Black Strength :") ||
2692                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2693                 looking_at(buf, &i, "<10>") ||
2694                 looking_at(buf, &i, "#@#")) {
2695                 /* Wrong board style */
2696                 loggedOn = TRUE;
2697                 SendToICS(ics_prefix);
2698                 SendToICS("set style 12\n");
2699                 SendToICS(ics_prefix);
2700                 SendToICS("refresh\n");
2701                 continue;
2702             }
2703             
2704             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2705                 ICSInitScript();
2706                 have_sent_ICS_logon = 1;
2707                 continue;
2708             }
2709               
2710             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2711                 (looking_at(buf, &i, "\n<12> ") ||
2712                  looking_at(buf, &i, "<12> "))) {
2713                 loggedOn = TRUE;
2714                 if (oldi > next_out) {
2715                     SendToPlayer(&buf[next_out], oldi - next_out);
2716                 }
2717                 next_out = i;
2718                 started = STARTED_BOARD;
2719                 parse_pos = 0;
2720                 continue;
2721             }
2722
2723             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2724                 looking_at(buf, &i, "<b1> ")) {
2725                 if (oldi > next_out) {
2726                     SendToPlayer(&buf[next_out], oldi - next_out);
2727                 }
2728                 next_out = i;
2729                 started = STARTED_HOLDINGS;
2730                 parse_pos = 0;
2731                 continue;
2732             }
2733
2734             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2735                 loggedOn = TRUE;
2736                 /* Header for a move list -- first line */
2737
2738                 switch (ics_getting_history) {
2739                   case H_FALSE:
2740                     switch (gameMode) {
2741                       case IcsIdle:
2742                       case BeginningOfGame:
2743                         /* User typed "moves" or "oldmoves" while we
2744                            were idle.  Pretend we asked for these
2745                            moves and soak them up so user can step
2746                            through them and/or save them.
2747                            */
2748                         Reset(FALSE, TRUE);
2749                         gameMode = IcsObserving;
2750                         ModeHighlight();
2751                         ics_gamenum = -1;
2752                         ics_getting_history = H_GOT_UNREQ_HEADER;
2753                         break;
2754                       case EditGame: /*?*/
2755                       case EditPosition: /*?*/
2756                         /* Should above feature work in these modes too? */
2757                         /* For now it doesn't */
2758                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2759                         break;
2760                       default:
2761                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2762                         break;
2763                     }
2764                     break;
2765                   case H_REQUESTED:
2766                     /* Is this the right one? */
2767                     if (gameInfo.white && gameInfo.black &&
2768                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2769                         strcmp(gameInfo.black, star_match[2]) == 0) {
2770                         /* All is well */
2771                         ics_getting_history = H_GOT_REQ_HEADER;
2772                     }
2773                     break;
2774                   case H_GOT_REQ_HEADER:
2775                   case H_GOT_UNREQ_HEADER:
2776                   case H_GOT_UNWANTED_HEADER:
2777                   case H_GETTING_MOVES:
2778                     /* Should not happen */
2779                     DisplayError(_("Error gathering move list: two headers"), 0);
2780                     ics_getting_history = H_FALSE;
2781                     break;
2782                 }
2783
2784                 /* Save player ratings into gameInfo if needed */
2785                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2786                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2787                     (gameInfo.whiteRating == -1 ||
2788                      gameInfo.blackRating == -1)) {
2789
2790                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2791                     gameInfo.blackRating = string_to_rating(star_match[3]);
2792                     if (appData.debugMode)
2793                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2794                               gameInfo.whiteRating, gameInfo.blackRating);
2795                 }
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i,
2800               "* * match, initial time: * minute*, increment: * second")) {
2801                 /* Header for a move list -- second line */
2802                 /* Initial board will follow if this is a wild game */
2803                 if (gameInfo.event != NULL) free(gameInfo.event);
2804                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2805                 gameInfo.event = StrSave(str);
2806                 /* [HGM] we switched variant. Translate boards if needed. */
2807                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2808                 continue;
2809             }
2810
2811             if (looking_at(buf, &i, "Move  ")) {
2812                 /* Beginning of a move list */
2813                 switch (ics_getting_history) {
2814                   case H_FALSE:
2815                     /* Normally should not happen */
2816                     /* Maybe user hit reset while we were parsing */
2817                     break;
2818                   case H_REQUESTED:
2819                     /* Happens if we are ignoring a move list that is not
2820                      * the one we just requested.  Common if the user
2821                      * tries to observe two games without turning off
2822                      * getMoveList */
2823                     break;
2824                   case H_GETTING_MOVES:
2825                     /* Should not happen */
2826                     DisplayError(_("Error gathering move list: nested"), 0);
2827                     ics_getting_history = H_FALSE;
2828                     break;
2829                   case H_GOT_REQ_HEADER:
2830                     ics_getting_history = H_GETTING_MOVES;
2831                     started = STARTED_MOVES;
2832                     parse_pos = 0;
2833                     if (oldi > next_out) {
2834                         SendToPlayer(&buf[next_out], oldi - next_out);
2835                     }
2836                     break;
2837                   case H_GOT_UNREQ_HEADER:
2838                     ics_getting_history = H_GETTING_MOVES;
2839                     started = STARTED_MOVES_NOHIDE;
2840                     parse_pos = 0;
2841                     break;
2842                   case H_GOT_UNWANTED_HEADER:
2843                     ics_getting_history = H_FALSE;
2844                     break;
2845                 }
2846                 continue;
2847             }                           
2848             
2849             if (looking_at(buf, &i, "% ") ||
2850                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2851                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2852                 savingComment = FALSE;
2853                 switch (started) {
2854                   case STARTED_MOVES:
2855                   case STARTED_MOVES_NOHIDE:
2856                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2857                     parse[parse_pos + i - oldi] = NULLCHAR;
2858                     ParseGameHistory(parse);
2859 #if ZIPPY
2860                     if (appData.zippyPlay && first.initDone) {
2861                         FeedMovesToProgram(&first, forwardMostMove);
2862                         if (gameMode == IcsPlayingWhite) {
2863                             if (WhiteOnMove(forwardMostMove)) {
2864                                 if (first.sendTime) {
2865                                   if (first.useColors) {
2866                                     SendToProgram("black\n", &first); 
2867                                   }
2868                                   SendTimeRemaining(&first, TRUE);
2869                                 }
2870                                 if (first.useColors) {
2871                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2872                                 }
2873                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2874                                 first.maybeThinking = TRUE;
2875                             } else {
2876                                 if (first.usePlayother) {
2877                                   if (first.sendTime) {
2878                                     SendTimeRemaining(&first, TRUE);
2879                                   }
2880                                   SendToProgram("playother\n", &first);
2881                                   firstMove = FALSE;
2882                                 } else {
2883                                   firstMove = TRUE;
2884                                 }
2885                             }
2886                         } else if (gameMode == IcsPlayingBlack) {
2887                             if (!WhiteOnMove(forwardMostMove)) {
2888                                 if (first.sendTime) {
2889                                   if (first.useColors) {
2890                                     SendToProgram("white\n", &first);
2891                                   }
2892                                   SendTimeRemaining(&first, FALSE);
2893                                 }
2894                                 if (first.useColors) {
2895                                   SendToProgram("black\n", &first);
2896                                 }
2897                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2898                                 first.maybeThinking = TRUE;
2899                             } else {
2900                                 if (first.usePlayother) {
2901                                   if (first.sendTime) {
2902                                     SendTimeRemaining(&first, FALSE);
2903                                   }
2904                                   SendToProgram("playother\n", &first);
2905                                   firstMove = FALSE;
2906                                 } else {
2907                                   firstMove = TRUE;
2908                                 }
2909                             }
2910                         }                       
2911                     }
2912 #endif
2913                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2914                         /* Moves came from oldmoves or moves command
2915                            while we weren't doing anything else.
2916                            */
2917                         currentMove = forwardMostMove;
2918                         ClearHighlights();/*!!could figure this out*/
2919                         flipView = appData.flipView;
2920                         DrawPosition(TRUE, boards[currentMove]);
2921                         DisplayBothClocks();
2922                         sprintf(str, "%s vs. %s",
2923                                 gameInfo.white, gameInfo.black);
2924                         DisplayTitle(str);
2925                         gameMode = IcsIdle;
2926                     } else {
2927                         /* Moves were history of an active game */
2928                         if (gameInfo.resultDetails != NULL) {
2929                             free(gameInfo.resultDetails);
2930                             gameInfo.resultDetails = NULL;
2931                         }
2932                     }
2933                     HistorySet(parseList, backwardMostMove,
2934                                forwardMostMove, currentMove-1);
2935                     DisplayMove(currentMove - 1);
2936                     if (started == STARTED_MOVES) next_out = i;
2937                     started = STARTED_NONE;
2938                     ics_getting_history = H_FALSE;
2939                     break;
2940
2941                   case STARTED_OBSERVE:
2942                     started = STARTED_NONE;
2943                     SendToICS(ics_prefix);
2944                     SendToICS("refresh\n");
2945                     break;
2946
2947                   default:
2948                     break;
2949                 }
2950                 if(bookHit) { // [HGM] book: simulate book reply
2951                     static char bookMove[MSG_SIZ]; // a bit generous?
2952
2953                     programStats.nodes = programStats.depth = programStats.time = 
2954                     programStats.score = programStats.got_only_move = 0;
2955                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2956
2957                     strcpy(bookMove, "move ");
2958                     strcat(bookMove, bookHit);
2959                     HandleMachineMove(bookMove, &first);
2960                 }
2961                 continue;
2962             }
2963             
2964             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2965                  started == STARTED_HOLDINGS ||
2966                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2967                 /* Accumulate characters in move list or board */
2968                 parse[parse_pos++] = buf[i];
2969             }
2970             
2971             /* Start of game messages.  Mostly we detect start of game
2972                when the first board image arrives.  On some versions
2973                of the ICS, though, we need to do a "refresh" after starting
2974                to observe in order to get the current board right away. */
2975             if (looking_at(buf, &i, "Adding game * to observation list")) {
2976                 started = STARTED_OBSERVE;
2977                 continue;
2978             }
2979
2980             /* Handle auto-observe */
2981             if (appData.autoObserve &&
2982                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2983                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2984                 char *player;
2985                 /* Choose the player that was highlighted, if any. */
2986                 if (star_match[0][0] == '\033' ||
2987                     star_match[1][0] != '\033') {
2988                     player = star_match[0];
2989                 } else {
2990                     player = star_match[2];
2991                 }
2992                 sprintf(str, "%sobserve %s\n",
2993                         ics_prefix, StripHighlightAndTitle(player));
2994                 SendToICS(str);
2995
2996                 /* Save ratings from notify string */
2997                 strcpy(player1Name, star_match[0]);
2998                 player1Rating = string_to_rating(star_match[1]);
2999                 strcpy(player2Name, star_match[2]);
3000                 player2Rating = string_to_rating(star_match[3]);
3001
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, 
3004                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3005                           player1Name, player1Rating,
3006                           player2Name, player2Rating);
3007
3008                 continue;
3009             }
3010
3011             /* Deal with automatic examine mode after a game,
3012                and with IcsObserving -> IcsExamining transition */
3013             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3014                 looking_at(buf, &i, "has made you an examiner of game *")) {
3015
3016                 int gamenum = atoi(star_match[0]);
3017                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3018                     gamenum == ics_gamenum) {
3019                     /* We were already playing or observing this game;
3020                        no need to refetch history */
3021                     gameMode = IcsExamining;
3022                     if (pausing) {
3023                         pauseExamForwardMostMove = forwardMostMove;
3024                     } else if (currentMove < forwardMostMove) {
3025                         ForwardInner(forwardMostMove);
3026                     }
3027                 } else {
3028                     /* I don't think this case really can happen */
3029                     SendToICS(ics_prefix);
3030                     SendToICS("refresh\n");
3031                 }
3032                 continue;
3033             }    
3034             
3035             /* Error messages */
3036 //          if (ics_user_moved) {
3037             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3038                 if (looking_at(buf, &i, "Illegal move") ||
3039                     looking_at(buf, &i, "Not a legal move") ||
3040                     looking_at(buf, &i, "Your king is in check") ||
3041                     looking_at(buf, &i, "It isn't your turn") ||
3042                     looking_at(buf, &i, "It is not your move")) {
3043                     /* Illegal move */
3044                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3045                         currentMove = --forwardMostMove;
3046                         DisplayMove(currentMove - 1); /* before DMError */
3047                         DrawPosition(FALSE, boards[currentMove]);
3048                         SwitchClocks();
3049                         DisplayBothClocks();
3050                     }
3051                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3052                     ics_user_moved = 0;
3053                     continue;
3054                 }
3055             }
3056
3057             if (looking_at(buf, &i, "still have time") ||
3058                 looking_at(buf, &i, "not out of time") ||
3059                 looking_at(buf, &i, "either player is out of time") ||
3060                 looking_at(buf, &i, "has timeseal; checking")) {
3061                 /* We must have called his flag a little too soon */
3062                 whiteFlag = blackFlag = FALSE;
3063                 continue;
3064             }
3065
3066             if (looking_at(buf, &i, "added * seconds to") ||
3067                 looking_at(buf, &i, "seconds were added to")) {
3068                 /* Update the clocks */
3069                 SendToICS(ics_prefix);
3070                 SendToICS("refresh\n");
3071                 continue;
3072             }
3073
3074             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3075                 ics_clock_paused = TRUE;
3076                 StopClocks();
3077                 continue;
3078             }
3079
3080             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3081                 ics_clock_paused = FALSE;
3082                 StartClocks();
3083                 continue;
3084             }
3085
3086             /* Grab player ratings from the Creating: message.
3087                Note we have to check for the special case when
3088                the ICS inserts things like [white] or [black]. */
3089             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3090                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3091                 /* star_matches:
3092                    0    player 1 name (not necessarily white)
3093                    1    player 1 rating
3094                    2    empty, white, or black (IGNORED)
3095                    3    player 2 name (not necessarily black)
3096                    4    player 2 rating
3097                    
3098                    The names/ratings are sorted out when the game
3099                    actually starts (below).
3100                 */
3101                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3102                 player1Rating = string_to_rating(star_match[1]);
3103                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3104                 player2Rating = string_to_rating(star_match[4]);
3105
3106                 if (appData.debugMode)
3107                   fprintf(debugFP, 
3108                           "Ratings from 'Creating:' %s %d, %s %d\n",
3109                           player1Name, player1Rating,
3110                           player2Name, player2Rating);
3111
3112                 continue;
3113             }
3114             
3115             /* Improved generic start/end-of-game messages */
3116             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3117                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3118                 /* If tkind == 0: */
3119                 /* star_match[0] is the game number */
3120                 /*           [1] is the white player's name */
3121                 /*           [2] is the black player's name */
3122                 /* For end-of-game: */
3123                 /*           [3] is the reason for the game end */
3124                 /*           [4] is a PGN end game-token, preceded by " " */
3125                 /* For start-of-game: */
3126                 /*           [3] begins with "Creating" or "Continuing" */
3127                 /*           [4] is " *" or empty (don't care). */
3128                 int gamenum = atoi(star_match[0]);
3129                 char *whitename, *blackname, *why, *endtoken;
3130                 ChessMove endtype = (ChessMove) 0;
3131
3132                 if (tkind == 0) {
3133                   whitename = star_match[1];
3134                   blackname = star_match[2];
3135                   why = star_match[3];
3136                   endtoken = star_match[4];
3137                 } else {
3138                   whitename = star_match[1];
3139                   blackname = star_match[3];
3140                   why = star_match[5];
3141                   endtoken = star_match[6];
3142                 }
3143
3144                 /* Game start messages */
3145                 if (strncmp(why, "Creating ", 9) == 0 ||
3146                     strncmp(why, "Continuing ", 11) == 0) {
3147                     gs_gamenum = gamenum;
3148                     strcpy(gs_kind, strchr(why, ' ') + 1);
3149 #if ZIPPY
3150                     if (appData.zippyPlay) {
3151                         ZippyGameStart(whitename, blackname);
3152                     }
3153 #endif /*ZIPPY*/
3154                     continue;
3155                 }
3156
3157                 /* Game end messages */
3158                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3159                     ics_gamenum != gamenum) {
3160                     continue;
3161                 }
3162                 while (endtoken[0] == ' ') endtoken++;
3163                 switch (endtoken[0]) {
3164                   case '*':
3165                   default:
3166                     endtype = GameUnfinished;
3167                     break;
3168                   case '0':
3169                     endtype = BlackWins;
3170                     break;
3171                   case '1':
3172                     if (endtoken[1] == '/')
3173                       endtype = GameIsDrawn;
3174                     else
3175                       endtype = WhiteWins;
3176                     break;
3177                 }
3178                 GameEnds(endtype, why, GE_ICS);
3179 #if ZIPPY
3180                 if (appData.zippyPlay && first.initDone) {
3181                     ZippyGameEnd(endtype, why);
3182                     if (first.pr == NULL) {
3183                       /* Start the next process early so that we'll
3184                          be ready for the next challenge */
3185                       StartChessProgram(&first);
3186                     }
3187                     /* Send "new" early, in case this command takes
3188                        a long time to finish, so that we'll be ready
3189                        for the next challenge. */
3190                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3191                     Reset(TRUE, TRUE);
3192                 }
3193 #endif /*ZIPPY*/
3194                 continue;
3195             }
3196
3197             if (looking_at(buf, &i, "Removing game * from observation") ||
3198                 looking_at(buf, &i, "no longer observing game *") ||
3199                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3200                 if (gameMode == IcsObserving &&
3201                     atoi(star_match[0]) == ics_gamenum)
3202                   {
3203                       /* icsEngineAnalyze */
3204                       if (appData.icsEngineAnalyze) {
3205                             ExitAnalyzeMode();
3206                             ModeHighlight();
3207                       }
3208                       StopClocks();
3209                       gameMode = IcsIdle;
3210                       ics_gamenum = -1;
3211                       ics_user_moved = FALSE;
3212                   }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i, "no longer examining game *")) {
3217                 if (gameMode == IcsExamining &&
3218                     atoi(star_match[0]) == ics_gamenum)
3219                   {
3220                       gameMode = IcsIdle;
3221                       ics_gamenum = -1;
3222                       ics_user_moved = FALSE;
3223                   }
3224                 continue;
3225             }
3226
3227             /* Advance leftover_start past any newlines we find,
3228                so only partial lines can get reparsed */
3229             if (looking_at(buf, &i, "\n")) {
3230                 prevColor = curColor;
3231                 if (curColor != ColorNormal) {
3232                     if (oldi > next_out) {
3233                         SendToPlayer(&buf[next_out], oldi - next_out);
3234                         next_out = oldi;
3235                     }
3236                     Colorize(ColorNormal, FALSE);
3237                     curColor = ColorNormal;
3238                 }
3239                 if (started == STARTED_BOARD) {
3240                     started = STARTED_NONE;
3241                     parse[parse_pos] = NULLCHAR;
3242                     ParseBoard12(parse);
3243                     ics_user_moved = 0;
3244
3245                     /* Send premove here */
3246                     if (appData.premove) {
3247                       char str[MSG_SIZ];
3248                       if (currentMove == 0 &&
3249                           gameMode == IcsPlayingWhite &&
3250                           appData.premoveWhite) {
3251                         sprintf(str, "%s\n", appData.premoveWhiteText);
3252                         if (appData.debugMode)
3253                           fprintf(debugFP, "Sending premove:\n");
3254                         SendToICS(str);
3255                       } else if (currentMove == 1 &&
3256                                  gameMode == IcsPlayingBlack &&
3257                                  appData.premoveBlack) {
3258                         sprintf(str, "%s\n", appData.premoveBlackText);
3259                         if (appData.debugMode)
3260                           fprintf(debugFP, "Sending premove:\n");
3261                         SendToICS(str);
3262                       } else if (gotPremove) {
3263                         gotPremove = 0;
3264                         ClearPremoveHighlights();
3265                         if (appData.debugMode)
3266                           fprintf(debugFP, "Sending premove:\n");
3267                           UserMoveEvent(premoveFromX, premoveFromY, 
3268                                         premoveToX, premoveToY, 
3269                                         premovePromoChar);
3270                       }
3271                     }
3272
3273                     /* Usually suppress following prompt */
3274                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3275                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3276                         if (looking_at(buf, &i, "*% ")) {
3277                             savingComment = FALSE;
3278                         }
3279                     }
3280                     next_out = i;
3281                 } else if (started == STARTED_HOLDINGS) {
3282                     int gamenum;
3283                     char new_piece[MSG_SIZ];
3284                     started = STARTED_NONE;
3285                     parse[parse_pos] = NULLCHAR;
3286                     if (appData.debugMode)
3287                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3288                                                         parse, currentMove);
3289                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3290                         gamenum == ics_gamenum) {
3291                         if (gameInfo.variant == VariantNormal) {
3292                           /* [HGM] We seem to switch variant during a game!
3293                            * Presumably no holdings were displayed, so we have
3294                            * to move the position two files to the right to
3295                            * create room for them!
3296                            */
3297                           VariantClass newVariant;
3298                           switch(gameInfo.boardWidth) { // base guess on board width
3299                                 case 9:  newVariant = VariantShogi; break;
3300                                 case 10: newVariant = VariantGreat; break;
3301                                 default: newVariant = VariantCrazyhouse; break;
3302                           }
3303                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3304                           /* Get a move list just to see the header, which
3305                              will tell us whether this is really bug or zh */
3306                           if (ics_getting_history == H_FALSE) {
3307                             ics_getting_history = H_REQUESTED;
3308                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3309                             SendToICS(str);
3310                           }
3311                         }
3312                         new_piece[0] = NULLCHAR;
3313                         sscanf(parse, "game %d white [%s black [%s <- %s",
3314                                &gamenum, white_holding, black_holding,
3315                                new_piece);
3316                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3317                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3318                         /* [HGM] copy holdings to board holdings area */
3319                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3320                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3321                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3322 #if ZIPPY
3323                         if (appData.zippyPlay && first.initDone) {
3324                             ZippyHoldings(white_holding, black_holding,
3325                                           new_piece);
3326                         }
3327 #endif /*ZIPPY*/
3328                         if (tinyLayout || smallLayout) {
3329                             char wh[16], bh[16];
3330                             PackHolding(wh, white_holding);
3331                             PackHolding(bh, black_holding);
3332                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3333                                     gameInfo.white, gameInfo.black);
3334                         } else {
3335                             sprintf(str, "%s [%s] vs. %s [%s]",
3336                                     gameInfo.white, white_holding,
3337                                     gameInfo.black, black_holding);
3338                         }
3339
3340                         DrawPosition(FALSE, boards[currentMove]);
3341                         DisplayTitle(str);
3342                     }
3343                     /* Suppress following prompt */
3344                     if (looking_at(buf, &i, "*% ")) {
3345                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3346                         savingComment = FALSE;
3347                     }
3348                     next_out = i;
3349                 }
3350                 continue;
3351             }
3352
3353             i++;                /* skip unparsed character and loop back */
3354         }
3355         
3356         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3357             started != STARTED_HOLDINGS && i > next_out) {
3358             SendToPlayer(&buf[next_out], i - next_out);
3359             next_out = i;
3360         }
3361         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3362         
3363         leftover_len = buf_len - leftover_start;
3364         /* if buffer ends with something we couldn't parse,
3365            reparse it after appending the next read */
3366         
3367     } else if (count == 0) {
3368         RemoveInputSource(isr);
3369         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3370     } else {
3371         DisplayFatalError(_("Error reading from ICS"), error, 1);
3372     }
3373 }
3374
3375
3376 /* Board style 12 looks like this:
3377    
3378    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3379    
3380  * The "<12> " is stripped before it gets to this routine.  The two
3381  * trailing 0's (flip state and clock ticking) are later addition, and
3382  * some chess servers may not have them, or may have only the first.
3383  * Additional trailing fields may be added in the future.  
3384  */
3385
3386 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3387
3388 #define RELATION_OBSERVING_PLAYED    0
3389 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3390 #define RELATION_PLAYING_MYMOVE      1
3391 #define RELATION_PLAYING_NOTMYMOVE  -1
3392 #define RELATION_EXAMINING           2
3393 #define RELATION_ISOLATED_BOARD     -3
3394 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3395
3396 void
3397 ParseBoard12(string)
3398      char *string;
3399
3400     GameMode newGameMode;
3401     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3402     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3403     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3404     char to_play, board_chars[200];
3405     char move_str[500], str[500], elapsed_time[500];
3406     char black[32], white[32];
3407     Board board;
3408     int prevMove = currentMove;
3409     int ticking = 2;
3410     ChessMove moveType;
3411     int fromX, fromY, toX, toY;
3412     char promoChar;
3413     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3414     char *bookHit = NULL; // [HGM] book
3415     Boolean weird = FALSE, reqFlag = FALSE;
3416
3417     fromX = fromY = toX = toY = -1;
3418     
3419     newGame = FALSE;
3420
3421     if (appData.debugMode)
3422       fprintf(debugFP, _("Parsing board: %s\n"), string);
3423
3424     move_str[0] = NULLCHAR;
3425     elapsed_time[0] = NULLCHAR;
3426     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3427         int  i = 0, j;
3428         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3429             if(string[i] == ' ') { ranks++; files = 0; }
3430             else files++;
3431             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3432             i++;
3433         }
3434         for(j = 0; j <i; j++) board_chars[j] = string[j];
3435         board_chars[i] = '\0';
3436         string += i + 1;
3437     }
3438     n = sscanf(string, PATTERN, &to_play, &double_push,
3439                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3440                &gamenum, white, black, &relation, &basetime, &increment,
3441                &white_stren, &black_stren, &white_time, &black_time,
3442                &moveNum, str, elapsed_time, move_str, &ics_flip,
3443                &ticking);
3444
3445     if (n < 21) {
3446         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3447         DisplayError(str, 0);
3448         return;
3449     }
3450
3451     /* Convert the move number to internal form */
3452     moveNum = (moveNum - 1) * 2;
3453     if (to_play == 'B') moveNum++;
3454     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3455       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3456                         0, 1);
3457       return;
3458     }
3459     
3460     switch (relation) {
3461       case RELATION_OBSERVING_PLAYED:
3462       case RELATION_OBSERVING_STATIC:
3463         if (gamenum == -1) {
3464             /* Old ICC buglet */
3465             relation = RELATION_OBSERVING_STATIC;
3466         }
3467         newGameMode = IcsObserving;
3468         break;
3469       case RELATION_PLAYING_MYMOVE:
3470       case RELATION_PLAYING_NOTMYMOVE:
3471         newGameMode =
3472           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3473             IcsPlayingWhite : IcsPlayingBlack;
3474         break;
3475       case RELATION_EXAMINING:
3476         newGameMode = IcsExamining;
3477         break;
3478       case RELATION_ISOLATED_BOARD:
3479       default:
3480         /* Just display this board.  If user was doing something else,
3481            we will forget about it until the next board comes. */ 
3482         newGameMode = IcsIdle;
3483         break;
3484       case RELATION_STARTING_POSITION:
3485         newGameMode = gameMode;
3486         break;
3487     }
3488     
3489     /* Modify behavior for initial board display on move listing
3490        of wild games.
3491        */
3492     switch (ics_getting_history) {
3493       case H_FALSE:
3494       case H_REQUESTED:
3495         break;
3496       case H_GOT_REQ_HEADER:
3497       case H_GOT_UNREQ_HEADER:
3498         /* This is the initial position of the current game */
3499         gamenum = ics_gamenum;
3500         moveNum = 0;            /* old ICS bug workaround */
3501         if (to_play == 'B') {
3502           startedFromSetupPosition = TRUE;
3503           blackPlaysFirst = TRUE;
3504           moveNum = 1;
3505           if (forwardMostMove == 0) forwardMostMove = 1;
3506           if (backwardMostMove == 0) backwardMostMove = 1;
3507           if (currentMove == 0) currentMove = 1;
3508         }
3509         newGameMode = gameMode;
3510         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3511         break;
3512       case H_GOT_UNWANTED_HEADER:
3513         /* This is an initial board that we don't want */
3514         return;
3515       case H_GETTING_MOVES:
3516         /* Should not happen */
3517         DisplayError(_("Error gathering move list: extra board"), 0);
3518         ics_getting_history = H_FALSE;
3519         return;
3520     }
3521
3522    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3523                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3524      /* [HGM] We seem to have switched variant unexpectedly
3525       * Try to guess new variant from board size
3526       */
3527           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3528           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3529           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3530           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3531           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3532           if(!weird) newVariant = VariantNormal;
3533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3534           /* Get a move list just to see the header, which
3535              will tell us whether this is really bug or zh */
3536           if (ics_getting_history == H_FALSE) {
3537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3538             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3539             SendToICS(str);
3540           }
3541     }
3542     
3543     /* Take action if this is the first board of a new game, or of a
3544        different game than is currently being displayed.  */
3545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3546         relation == RELATION_ISOLATED_BOARD) {
3547         
3548         /* Forget the old game and get the history (if any) of the new one */
3549         if (gameMode != BeginningOfGame) {
3550           Reset(TRUE, TRUE);
3551         }
3552         newGame = TRUE;
3553         if (appData.autoRaiseBoard) BoardToTop();
3554         prevMove = -3;
3555         if (gamenum == -1) {
3556             newGameMode = IcsIdle;
3557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3558                    appData.getMoveList && !reqFlag) {
3559             /* Need to get game history */
3560             ics_getting_history = H_REQUESTED;
3561             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3562             SendToICS(str);
3563         }
3564         
3565         /* Initially flip the board to have black on the bottom if playing
3566            black or if the ICS flip flag is set, but let the user change
3567            it with the Flip View button. */
3568         flipView = appData.autoFlipView ? 
3569           (newGameMode == IcsPlayingBlack) || ics_flip :
3570           appData.flipView;
3571         
3572         /* Done with values from previous mode; copy in new ones */
3573         gameMode = newGameMode;
3574         ModeHighlight();
3575         ics_gamenum = gamenum;
3576         if (gamenum == gs_gamenum) {
3577             int klen = strlen(gs_kind);
3578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3579             sprintf(str, "ICS %s", gs_kind);
3580             gameInfo.event = StrSave(str);
3581         } else {
3582             gameInfo.event = StrSave("ICS game");
3583         }
3584         gameInfo.site = StrSave(appData.icsHost);
3585         gameInfo.date = PGNDate();
3586         gameInfo.round = StrSave("-");
3587         gameInfo.white = StrSave(white);
3588         gameInfo.black = StrSave(black);
3589         timeControl = basetime * 60 * 1000;
3590         timeControl_2 = 0;
3591         timeIncrement = increment * 1000;
3592         movesPerSession = 0;
3593         gameInfo.timeControl = TimeControlTagValue();
3594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3595   if (appData.debugMode) {
3596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3598     setbuf(debugFP, NULL);
3599   }
3600
3601         gameInfo.outOfBook = NULL;
3602         
3603         /* Do we have the ratings? */
3604         if (strcmp(player1Name, white) == 0 &&
3605             strcmp(player2Name, black) == 0) {
3606             if (appData.debugMode)
3607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3608                       player1Rating, player2Rating);
3609             gameInfo.whiteRating = player1Rating;
3610             gameInfo.blackRating = player2Rating;
3611         } else if (strcmp(player2Name, white) == 0 &&
3612                    strcmp(player1Name, black) == 0) {
3613             if (appData.debugMode)
3614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3615                       player2Rating, player1Rating);
3616             gameInfo.whiteRating = player2Rating;
3617             gameInfo.blackRating = player1Rating;
3618         }
3619         player1Name[0] = player2Name[0] = NULLCHAR;
3620
3621         /* Silence shouts if requested */
3622         if (appData.quietPlay &&
3623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3624             SendToICS(ics_prefix);
3625             SendToICS("set shout 0\n");
3626         }
3627     }
3628     
3629     /* Deal with midgame name changes */
3630     if (!newGame) {
3631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3632             if (gameInfo.white) free(gameInfo.white);
3633             gameInfo.white = StrSave(white);
3634         }
3635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3636             if (gameInfo.black) free(gameInfo.black);
3637             gameInfo.black = StrSave(black);
3638         }
3639     }
3640     
3641     /* Throw away game result if anything actually changes in examine mode */
3642     if (gameMode == IcsExamining && !newGame) {
3643         gameInfo.result = GameUnfinished;
3644         if (gameInfo.resultDetails != NULL) {
3645             free(gameInfo.resultDetails);
3646             gameInfo.resultDetails = NULL;
3647         }
3648     }
3649     
3650     /* In pausing && IcsExamining mode, we ignore boards coming
3651        in if they are in a different variation than we are. */
3652     if (pauseExamInvalid) return;
3653     if (pausing && gameMode == IcsExamining) {
3654         if (moveNum <= pauseExamForwardMostMove) {
3655             pauseExamInvalid = TRUE;
3656             forwardMostMove = pauseExamForwardMostMove;
3657             return;
3658         }
3659     }
3660     
3661   if (appData.debugMode) {
3662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3663   }
3664     /* Parse the board */
3665     for (k = 0; k < ranks; k++) {
3666       for (j = 0; j < files; j++)
3667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3668       if(gameInfo.holdingsWidth > 1) {
3669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3671       }
3672     }
3673     CopyBoard(boards[moveNum], board);
3674     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3675     if (moveNum == 0) {
3676         startedFromSetupPosition =
3677           !CompareBoards(board, initialPosition);
3678         if(startedFromSetupPosition)
3679             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3680     }
3681
3682     /* [HGM] Set castling rights. Take the outermost Rooks,
3683        to make it also work for FRC opening positions. Note that board12
3684        is really defective for later FRC positions, as it has no way to
3685        indicate which Rook can castle if they are on the same side of King.
3686        For the initial position we grant rights to the outermost Rooks,
3687        and remember thos rights, and we then copy them on positions
3688        later in an FRC game. This means WB might not recognize castlings with
3689        Rooks that have moved back to their original position as illegal,
3690        but in ICS mode that is not its job anyway.
3691     */
3692     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3693     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3694
3695         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3696             if(board[0][i] == WhiteRook) j = i;
3697         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3698         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3699             if(board[0][i] == WhiteRook) j = i;
3700         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3702             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707
3708         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3709         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3710             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712             if(board[BOARD_HEIGHT-1][k] == bKing)
3713                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3714     } else { int r;
3715         r = boards[moveNum][CASTLING][0] = initialRights[0];
3716         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3717         r = boards[moveNum][CASTLING][1] = initialRights[1];
3718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3719         r = boards[moveNum][CASTLING][3] = initialRights[3];
3720         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3721         r = boards[moveNum][CASTLING][4] = initialRights[4];
3722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3723         /* wildcastle kludge: always assume King has rights */
3724         r = boards[moveNum][CASTLING][2] = initialRights[2];
3725         r = boards[moveNum][CASTLING][5] = initialRights[5];
3726     }
3727     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3728     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3729
3730     
3731     if (ics_getting_history == H_GOT_REQ_HEADER ||
3732         ics_getting_history == H_GOT_UNREQ_HEADER) {
3733         /* This was an initial position from a move list, not
3734            the current position */
3735         return;
3736     }
3737     
3738     /* Update currentMove and known move number limits */
3739     newMove = newGame || moveNum > forwardMostMove;
3740
3741     if (newGame) {
3742         forwardMostMove = backwardMostMove = currentMove = moveNum;
3743         if (gameMode == IcsExamining && moveNum == 0) {
3744           /* Workaround for ICS limitation: we are not told the wild
3745              type when starting to examine a game.  But if we ask for
3746              the move list, the move list header will tell us */
3747             ics_getting_history = H_REQUESTED;
3748             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3749             SendToICS(str);
3750         }
3751     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3752                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3753 #if ZIPPY
3754         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3755         /* [HGM] applied this also to an engine that is silently watching        */
3756         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3757             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3758             gameInfo.variant == currentlyInitializedVariant) {
3759           takeback = forwardMostMove - moveNum;
3760           for (i = 0; i < takeback; i++) {
3761             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3762             SendToProgram("undo\n", &first);
3763           }
3764         }
3765 #endif
3766
3767         forwardMostMove = moveNum;
3768         if (!pausing || currentMove > forwardMostMove)
3769           currentMove = forwardMostMove;
3770     } else {
3771         /* New part of history that is not contiguous with old part */ 
3772         if (pausing && gameMode == IcsExamining) {
3773             pauseExamInvalid = TRUE;
3774             forwardMostMove = pauseExamForwardMostMove;
3775             return;
3776         }
3777         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3778 #if ZIPPY
3779             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3780                 // [HGM] when we will receive the move list we now request, it will be
3781                 // fed to the engine from the first move on. So if the engine is not
3782                 // in the initial position now, bring it there.
3783                 InitChessProgram(&first, 0);
3784             }
3785 #endif
3786             ics_getting_history = H_REQUESTED;
3787             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3788             SendToICS(str);
3789         }
3790         forwardMostMove = backwardMostMove = currentMove = moveNum;
3791     }
3792     
3793     /* Update the clocks */
3794     if (strchr(elapsed_time, '.')) {
3795       /* Time is in ms */
3796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3798     } else {
3799       /* Time is in seconds */
3800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3802     }
3803       
3804
3805 #if ZIPPY
3806     if (appData.zippyPlay && newGame &&
3807         gameMode != IcsObserving && gameMode != IcsIdle &&
3808         gameMode != IcsExamining)
3809       ZippyFirstBoard(moveNum, basetime, increment);
3810 #endif
3811     
3812     /* Put the move on the move list, first converting
3813        to canonical algebraic form. */
3814     if (moveNum > 0) {
3815   if (appData.debugMode) {
3816     if (appData.debugMode) { int f = forwardMostMove;
3817         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3818                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3819                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3820     }
3821     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3822     fprintf(debugFP, "moveNum = %d\n", moveNum);
3823     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3824     setbuf(debugFP, NULL);
3825   }
3826         if (moveNum <= backwardMostMove) {
3827             /* We don't know what the board looked like before
3828                this move.  Punt. */
3829             strcpy(parseList[moveNum - 1], move_str);
3830             strcat(parseList[moveNum - 1], " ");
3831             strcat(parseList[moveNum - 1], elapsed_time);
3832             moveList[moveNum - 1][0] = NULLCHAR;
3833         } else if (strcmp(move_str, "none") == 0) {
3834             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3835             /* Again, we don't know what the board looked like;
3836                this is really the start of the game. */
3837             parseList[moveNum - 1][0] = NULLCHAR;
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             backwardMostMove = moveNum;
3840             startedFromSetupPosition = TRUE;
3841             fromX = fromY = toX = toY = -1;
3842         } else {
3843           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3844           //                 So we parse the long-algebraic move string in stead of the SAN move
3845           int valid; char buf[MSG_SIZ], *prom;
3846
3847           // str looks something like "Q/a1-a2"; kill the slash
3848           if(str[1] == '/') 
3849                 sprintf(buf, "%c%s", str[0], str+2);
3850           else  strcpy(buf, str); // might be castling
3851           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3852                 strcat(buf, prom); // long move lacks promo specification!
3853           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3854                 if(appData.debugMode) 
3855                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3856                 strcpy(move_str, buf);
3857           }
3858           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3859                                 &fromX, &fromY, &toX, &toY, &promoChar)
3860                || ParseOneMove(buf, moveNum - 1, &moveType,
3861                                 &fromX, &fromY, &toX, &toY, &promoChar);
3862           // end of long SAN patch
3863           if (valid) {
3864             (void) CoordsToAlgebraic(boards[moveNum - 1],
3865                                      PosFlags(moveNum - 1),
3866                                      fromY, fromX, toY, toX, promoChar,
3867                                      parseList[moveNum-1]);
3868             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3869               case MT_NONE:
3870               case MT_STALEMATE:
3871               default:
3872                 break;
3873               case MT_CHECK:
3874                 if(gameInfo.variant != VariantShogi)
3875                     strcat(parseList[moveNum - 1], "+");
3876                 break;
3877               case MT_CHECKMATE:
3878               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3879                 strcat(parseList[moveNum - 1], "#");
3880                 break;
3881             }
3882             strcat(parseList[moveNum - 1], " ");
3883             strcat(parseList[moveNum - 1], elapsed_time);
3884             /* currentMoveString is set as a side-effect of ParseOneMove */
3885             strcpy(moveList[moveNum - 1], currentMoveString);
3886             strcat(moveList[moveNum - 1], "\n");
3887           } else {
3888             /* Move from ICS was illegal!?  Punt. */
3889   if (appData.debugMode) {
3890     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3891     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3892   }
3893             strcpy(parseList[moveNum - 1], move_str);
3894             strcat(parseList[moveNum - 1], " ");
3895             strcat(parseList[moveNum - 1], elapsed_time);
3896             moveList[moveNum - 1][0] = NULLCHAR;
3897             fromX = fromY = toX = toY = -1;
3898           }
3899         }
3900   if (appData.debugMode) {
3901     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3902     setbuf(debugFP, NULL);
3903   }
3904
3905 #if ZIPPY
3906         /* Send move to chess program (BEFORE animating it). */
3907         if (appData.zippyPlay && !newGame && newMove && 
3908            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3909
3910             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3911                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3912                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3913                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3914                             move_str);
3915                     DisplayError(str, 0);
3916                 } else {
3917                     if (first.sendTime) {
3918                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3919                     }
3920                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3921                     if (firstMove && !bookHit) {
3922                         firstMove = FALSE;
3923                         if (first.useColors) {
3924                           SendToProgram(gameMode == IcsPlayingWhite ?
3925                                         "white\ngo\n" :
3926                                         "black\ngo\n", &first);
3927                         } else {
3928                           SendToProgram("go\n", &first);
3929                         }
3930                         first.maybeThinking = TRUE;
3931                     }
3932                 }
3933             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3934               if (moveList[moveNum - 1][0] == NULLCHAR) {
3935                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3936                 DisplayError(str, 0);
3937               } else {
3938                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3939                 SendMoveToProgram(moveNum - 1, &first);
3940               }
3941             }
3942         }
3943 #endif
3944     }
3945
3946     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3947         /* If move comes from a remote source, animate it.  If it
3948            isn't remote, it will have already been animated. */
3949         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3950             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3951         }
3952         if (!pausing && appData.highlightLastMove) {
3953             SetHighlights(fromX, fromY, toX, toY);
3954         }
3955     }
3956     
3957     /* Start the clocks */
3958     whiteFlag = blackFlag = FALSE;
3959     appData.clockMode = !(basetime == 0 && increment == 0);
3960     if (ticking == 0) {
3961       ics_clock_paused = TRUE;
3962       StopClocks();
3963     } else if (ticking == 1) {
3964       ics_clock_paused = FALSE;
3965     }
3966     if (gameMode == IcsIdle ||
3967         relation == RELATION_OBSERVING_STATIC ||
3968         relation == RELATION_EXAMINING ||
3969         ics_clock_paused)
3970       DisplayBothClocks();
3971     else
3972       StartClocks();
3973     
3974     /* Display opponents and material strengths */
3975     if (gameInfo.variant != VariantBughouse &&
3976         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3977         if (tinyLayout || smallLayout) {
3978             if(gameInfo.variant == VariantNormal)
3979                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3981                     basetime, increment);
3982             else
3983                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3984                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3985                     basetime, increment, (int) gameInfo.variant);
3986         } else {
3987             if(gameInfo.variant == VariantNormal)
3988                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3989                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3990                     basetime, increment);
3991             else
3992                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3994                     basetime, increment, VariantName(gameInfo.variant));
3995         }
3996         DisplayTitle(str);
3997   if (appData.debugMode) {
3998     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3999   }
4000     }
4001
4002    
4003     /* Display the board */
4004     if (!pausing && !appData.noGUI) {
4005       
4006       if (appData.premove)
4007           if (!gotPremove || 
4008              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4009              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4010               ClearPremoveHighlights();
4011
4012       DrawPosition(FALSE, boards[currentMove]);
4013       DisplayMove(moveNum - 1);
4014       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4015             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4016               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4017         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4018       }
4019     }
4020
4021     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4022 #if ZIPPY
4023     if(bookHit) { // [HGM] book: simulate book reply
4024         static char bookMove[MSG_SIZ]; // a bit generous?
4025
4026         programStats.nodes = programStats.depth = programStats.time = 
4027         programStats.score = programStats.got_only_move = 0;
4028         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4029
4030         strcpy(bookMove, "move ");
4031         strcat(bookMove, bookHit);
4032         HandleMachineMove(bookMove, &first);
4033     }
4034 #endif
4035 }
4036
4037 void
4038 GetMoveListEvent()
4039 {
4040     char buf[MSG_SIZ];
4041     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4042         ics_getting_history = H_REQUESTED;
4043         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4044         SendToICS(buf);
4045     }
4046 }
4047
4048 void
4049 AnalysisPeriodicEvent(force)
4050      int force;
4051 {
4052     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4053          && !force) || !appData.periodicUpdates)
4054       return;
4055
4056     /* Send . command to Crafty to collect stats */
4057     SendToProgram(".\n", &first);
4058
4059     /* Don't send another until we get a response (this makes
4060        us stop sending to old Crafty's which don't understand
4061        the "." command (sending illegal cmds resets node count & time,
4062        which looks bad)) */
4063     programStats.ok_to_send = 0;
4064 }
4065
4066 void ics_update_width(new_width)
4067         int new_width;
4068 {
4069         ics_printf("set width %d\n", new_width);
4070 }
4071
4072 void
4073 SendMoveToProgram(moveNum, cps)
4074      int moveNum;
4075      ChessProgramState *cps;
4076 {
4077     char buf[MSG_SIZ];
4078
4079     if (cps->useUsermove) {
4080       SendToProgram("usermove ", cps);
4081     }
4082     if (cps->useSAN) {
4083       char *space;
4084       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4085         int len = space - parseList[moveNum];
4086         memcpy(buf, parseList[moveNum], len);
4087         buf[len++] = '\n';
4088         buf[len] = NULLCHAR;
4089       } else {
4090         sprintf(buf, "%s\n", parseList[moveNum]);
4091       }
4092       SendToProgram(buf, cps);
4093     } else {
4094       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4095         AlphaRank(moveList[moveNum], 4);
4096         SendToProgram(moveList[moveNum], cps);
4097         AlphaRank(moveList[moveNum], 4); // and back
4098       } else
4099       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4100        * the engine. It would be nice to have a better way to identify castle 
4101        * moves here. */
4102       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4103                                                                          && cps->useOOCastle) {
4104         int fromX = moveList[moveNum][0] - AAA; 
4105         int fromY = moveList[moveNum][1] - ONE;
4106         int toX = moveList[moveNum][2] - AAA; 
4107         int toY = moveList[moveNum][3] - ONE;
4108         if((boards[moveNum][fromY][fromX] == WhiteKing 
4109             && boards[moveNum][toY][toX] == WhiteRook)
4110            || (boards[moveNum][fromY][fromX] == BlackKing 
4111                && boards[moveNum][toY][toX] == BlackRook)) {
4112           if(toX > fromX) SendToProgram("O-O\n", cps);
4113           else SendToProgram("O-O-O\n", cps);
4114         }
4115         else SendToProgram(moveList[moveNum], cps);
4116       }
4117       else SendToProgram(moveList[moveNum], cps);
4118       /* End of additions by Tord */
4119     }
4120
4121     /* [HGM] setting up the opening has brought engine in force mode! */
4122     /*       Send 'go' if we are in a mode where machine should play. */
4123     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4124         (gameMode == TwoMachinesPlay   ||
4125 #ifdef ZIPPY
4126          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4127 #endif
4128          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4129         SendToProgram("go\n", cps);
4130   if (appData.debugMode) {
4131     fprintf(debugFP, "(extra)\n");
4132   }
4133     }
4134     setboardSpoiledMachineBlack = 0;
4135 }
4136
4137 void
4138 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4139      ChessMove moveType;
4140      int fromX, fromY, toX, toY;
4141 {
4142     char user_move[MSG_SIZ];
4143
4144     switch (moveType) {
4145       default:
4146         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4147                 (int)moveType, fromX, fromY, toX, toY);
4148         DisplayError(user_move + strlen("say "), 0);
4149         break;
4150       case WhiteKingSideCastle:
4151       case BlackKingSideCastle:
4152       case WhiteQueenSideCastleWild:
4153       case BlackQueenSideCastleWild:
4154       /* PUSH Fabien */
4155       case WhiteHSideCastleFR:
4156       case BlackHSideCastleFR:
4157       /* POP Fabien */
4158         sprintf(user_move, "o-o\n");
4159         break;
4160       case WhiteQueenSideCastle:
4161       case BlackQueenSideCastle:
4162       case WhiteKingSideCastleWild:
4163       case BlackKingSideCastleWild:
4164       /* PUSH Fabien */
4165       case WhiteASideCastleFR:
4166       case BlackASideCastleFR:
4167       /* POP Fabien */
4168         sprintf(user_move, "o-o-o\n");
4169         break;
4170       case WhitePromotionQueen:
4171       case BlackPromotionQueen:
4172       case WhitePromotionRook:
4173       case BlackPromotionRook:
4174       case WhitePromotionBishop:
4175       case BlackPromotionBishop:
4176       case WhitePromotionKnight:
4177       case BlackPromotionKnight:
4178       case WhitePromotionKing:
4179       case BlackPromotionKing:
4180       case WhitePromotionChancellor:
4181       case BlackPromotionChancellor:
4182       case WhitePromotionArchbishop:
4183       case BlackPromotionArchbishop:
4184         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4185             sprintf(user_move, "%c%c%c%c=%c\n",
4186                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4187                 PieceToChar(WhiteFerz));
4188         else if(gameInfo.variant == VariantGreat)
4189             sprintf(user_move, "%c%c%c%c=%c\n",
4190                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191                 PieceToChar(WhiteMan));
4192         else
4193             sprintf(user_move, "%c%c%c%c=%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195                 PieceToChar(PromoPiece(moveType)));
4196         break;
4197       case WhiteDrop:
4198       case BlackDrop:
4199         sprintf(user_move, "%c@%c%c\n",
4200                 ToUpper(PieceToChar((ChessSquare) fromX)),
4201                 AAA + toX, ONE + toY);
4202         break;
4203       case NormalMove:
4204       case WhiteCapturesEnPassant:
4205       case BlackCapturesEnPassant:
4206       case IllegalMove:  /* could be a variant we don't quite understand */
4207         sprintf(user_move, "%c%c%c%c\n",
4208                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4209         break;
4210     }
4211     SendToICS(user_move);
4212     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4213         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4214 }
4215
4216 void
4217 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4218      int rf, ff, rt, ft;
4219      char promoChar;
4220      char move[7];
4221 {
4222     if (rf == DROP_RANK) {
4223         sprintf(move, "%c@%c%c\n",
4224                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4225     } else {
4226         if (promoChar == 'x' || promoChar == NULLCHAR) {
4227             sprintf(move, "%c%c%c%c\n",
4228                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4229         } else {
4230             sprintf(move, "%c%c%c%c%c\n",
4231                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4232         }
4233     }
4234 }
4235
4236 void
4237 ProcessICSInitScript(f)
4238      FILE *f;
4239 {
4240     char buf[MSG_SIZ];
4241
4242     while (fgets(buf, MSG_SIZ, f)) {
4243         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4244     }
4245
4246     fclose(f);
4247 }
4248
4249
4250 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4251 void
4252 AlphaRank(char *move, int n)
4253 {
4254 //    char *p = move, c; int x, y;
4255
4256     if (appData.debugMode) {
4257         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4258     }
4259
4260     if(move[1]=='*' && 
4261        move[2]>='0' && move[2]<='9' &&
4262        move[3]>='a' && move[3]<='x'    ) {
4263         move[1] = '@';
4264         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4265         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4266     } else
4267     if(move[0]>='0' && move[0]<='9' &&
4268        move[1]>='a' && move[1]<='x' &&
4269        move[2]>='0' && move[2]<='9' &&
4270        move[3]>='a' && move[3]<='x'    ) {
4271         /* input move, Shogi -> normal */
4272         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4273         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4274         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4275         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4276     } else
4277     if(move[1]=='@' &&
4278        move[3]>='0' && move[3]<='9' &&
4279        move[2]>='a' && move[2]<='x'    ) {
4280         move[1] = '*';
4281         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4282         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4283     } else
4284     if(
4285        move[0]>='a' && move[0]<='x' &&
4286        move[3]>='0' && move[3]<='9' &&
4287        move[2]>='a' && move[2]<='x'    ) {
4288          /* output move, normal -> Shogi */
4289         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4290         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4291         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4292         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4293         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4294     }
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "   out = '%s'\n", move);
4297     }
4298 }
4299
4300 /* Parser for moves from gnuchess, ICS, or user typein box */
4301 Boolean
4302 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4303      char *move;
4304      int moveNum;
4305      ChessMove *moveType;
4306      int *fromX, *fromY, *toX, *toY;
4307      char *promoChar;
4308 {       
4309     if (appData.debugMode) {
4310         fprintf(debugFP, "move to parse: %s\n", move);
4311     }
4312     *moveType = yylexstr(moveNum, move);
4313
4314     switch (*moveType) {
4315       case WhitePromotionChancellor:
4316       case BlackPromotionChancellor:
4317       case WhitePromotionArchbishop:
4318       case BlackPromotionArchbishop:
4319       case WhitePromotionQueen:
4320       case BlackPromotionQueen:
4321       case WhitePromotionRook:
4322       case BlackPromotionRook:
4323       case WhitePromotionBishop:
4324       case BlackPromotionBishop:
4325       case WhitePromotionKnight:
4326       case BlackPromotionKnight:
4327       case WhitePromotionKing:
4328       case BlackPromotionKing:
4329       case NormalMove:
4330       case WhiteCapturesEnPassant:
4331       case BlackCapturesEnPassant:
4332       case WhiteKingSideCastle:
4333       case WhiteQueenSideCastle:
4334       case BlackKingSideCastle:
4335       case BlackQueenSideCastle:
4336       case WhiteKingSideCastleWild:
4337       case WhiteQueenSideCastleWild:
4338       case BlackKingSideCastleWild:
4339       case BlackQueenSideCastleWild:
4340       /* Code added by Tord: */
4341       case WhiteHSideCastleFR:
4342       case WhiteASideCastleFR:
4343       case BlackHSideCastleFR:
4344       case BlackASideCastleFR:
4345       /* End of code added by Tord */
4346       case IllegalMove:         /* bug or odd chess variant */
4347         *fromX = currentMoveString[0] - AAA;
4348         *fromY = currentMoveString[1] - ONE;
4349         *toX = currentMoveString[2] - AAA;
4350         *toY = currentMoveString[3] - ONE;
4351         *promoChar = currentMoveString[4];
4352         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4353             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4354     if (appData.debugMode) {
4355         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4356     }
4357             *fromX = *fromY = *toX = *toY = 0;
4358             return FALSE;
4359         }
4360         if (appData.testLegality) {
4361           return (*moveType != IllegalMove);
4362         } else {
4363           return !(fromX == fromY && toX == toY);
4364         }
4365
4366       case WhiteDrop:
4367       case BlackDrop:
4368         *fromX = *moveType == WhiteDrop ?
4369           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4370           (int) CharToPiece(ToLower(currentMoveString[0]));
4371         *fromY = DROP_RANK;
4372         *toX = currentMoveString[2] - AAA;
4373         *toY = currentMoveString[3] - ONE;
4374         *promoChar = NULLCHAR;
4375         return TRUE;
4376
4377       case AmbiguousMove:
4378       case ImpossibleMove:
4379       case (ChessMove) 0:       /* end of file */
4380       case ElapsedTime:
4381       case Comment:
4382       case PGNTag:
4383       case NAG:
4384       case WhiteWins:
4385       case BlackWins:
4386       case GameIsDrawn:
4387       default:
4388     if (appData.debugMode) {
4389         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4390     }
4391         /* bug? */
4392         *fromX = *fromY = *toX = *toY = 0;
4393         *promoChar = NULLCHAR;
4394         return FALSE;
4395     }
4396 }
4397
4398 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4399 // All positions will have equal probability, but the current method will not provide a unique
4400 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4401 #define DARK 1
4402 #define LITE 2
4403 #define ANY 3
4404
4405 int squaresLeft[4];
4406 int piecesLeft[(int)BlackPawn];
4407 int seed, nrOfShuffles;
4408
4409 void GetPositionNumber()
4410 {       // sets global variable seed
4411         int i;
4412
4413         seed = appData.defaultFrcPosition;
4414         if(seed < 0) { // randomize based on time for negative FRC position numbers
4415                 for(i=0; i<50; i++) seed += random();
4416                 seed = random() ^ random() >> 8 ^ random() << 8;
4417                 if(seed<0) seed = -seed;
4418         }
4419 }
4420
4421 int put(Board board, int pieceType, int rank, int n, int shade)
4422 // put the piece on the (n-1)-th empty squares of the given shade
4423 {
4424         int i;
4425
4426         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4427                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4428                         board[rank][i] = (ChessSquare) pieceType;
4429                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4430                         squaresLeft[ANY]--;
4431                         piecesLeft[pieceType]--; 
4432                         return i;
4433                 }
4434         }
4435         return -1;
4436 }
4437
4438
4439 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4440 // calculate where the next piece goes, (any empty square), and put it there
4441 {
4442         int i;
4443
4444         i = seed % squaresLeft[shade];
4445         nrOfShuffles *= squaresLeft[shade];
4446         seed /= squaresLeft[shade];
4447         put(board, pieceType, rank, i, shade);
4448 }
4449
4450 void AddTwoPieces(Board board, int pieceType, int rank)
4451 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4452 {
4453         int i, n=squaresLeft[ANY], j=n-1, k;
4454
4455         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4456         i = seed % k;  // pick one
4457         nrOfShuffles *= k;
4458         seed /= k;
4459         while(i >= j) i -= j--;
4460         j = n - 1 - j; i += j;
4461         put(board, pieceType, rank, j, ANY);
4462         put(board, pieceType, rank, i, ANY);
4463 }
4464
4465 void SetUpShuffle(Board board, int number)
4466 {
4467         int i, p, first=1;
4468
4469         GetPositionNumber(); nrOfShuffles = 1;
4470
4471         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4472         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4473         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4474
4475         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4476
4477         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4478             p = (int) board[0][i];
4479             if(p < (int) BlackPawn) piecesLeft[p] ++;
4480             board[0][i] = EmptySquare;
4481         }
4482
4483         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4484             // shuffles restricted to allow normal castling put KRR first
4485             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4486                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4487             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4488                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4489             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4490                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4491             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4492                 put(board, WhiteRook, 0, 0, ANY);
4493             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4494         }
4495
4496         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4497             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4498             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4499                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4500                 while(piecesLeft[p] >= 2) {
4501                     AddOnePiece(board, p, 0, LITE);
4502                     AddOnePiece(board, p, 0, DARK);
4503                 }
4504                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4505             }
4506
4507         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4508             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4509             // but we leave King and Rooks for last, to possibly obey FRC restriction
4510             if(p == (int)WhiteRook) continue;
4511             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4512             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4513         }
4514
4515         // now everything is placed, except perhaps King (Unicorn) and Rooks
4516
4517         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4518             // Last King gets castling rights
4519             while(piecesLeft[(int)WhiteUnicorn]) {
4520                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4521                 initialRights[2]  = initialRights[5]  = 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
5318     return moveType;
5319     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5320        function is made into one that returns an OK move type if FinishMove
5321        should be called. This to give the calling driver routine the
5322        opportunity to finish the userMove input with a promotion popup,
5323        without bothering the user with this for invalid or illegal moves */
5324
5325 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5326 }
5327
5328 /* Common tail of UserMoveEvent and DropMenuEvent */
5329 int
5330 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5331      ChessMove moveType;
5332      int fromX, fromY, toX, toY;
5333      /*char*/int promoChar;
5334 {
5335     char *bookHit = 0;
5336
5337     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5338         // [HGM] superchess: suppress promotions to non-available piece
5339         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340         if(WhiteOnMove(currentMove)) {
5341             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5342         } else {
5343             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5344         }
5345     }
5346
5347     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5348        move type in caller when we know the move is a legal promotion */
5349     if(moveType == NormalMove && promoChar)
5350         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5351
5352     /* [HGM] convert drag-and-drop piece drops to standard form */
5353     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5354          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5355            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5356                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5357            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5358            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5359            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5360            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5361          fromY = DROP_RANK;
5362     }
5363
5364     /* [HGM] <popupFix> The following if has been moved here from
5365        UserMoveEvent(). Because it seemed to belong here (why not allow
5366        piece drops in training games?), and because it can only be
5367        performed after it is known to what we promote. */
5368     if (gameMode == Training) {
5369       /* compare the move played on the board to the next move in the
5370        * game. If they match, display the move and the opponent's response. 
5371        * If they don't match, display an error message.
5372        */
5373       int saveAnimate;
5374       Board testBoard;
5375       CopyBoard(testBoard, boards[currentMove]);
5376       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5377
5378       if (CompareBoards(testBoard, boards[currentMove+1])) {
5379         ForwardInner(currentMove+1);
5380
5381         /* Autoplay the opponent's response.
5382          * if appData.animate was TRUE when Training mode was entered,
5383          * the response will be animated.
5384          */
5385         saveAnimate = appData.animate;
5386         appData.animate = animateTraining;
5387         ForwardInner(currentMove+1);
5388         appData.animate = saveAnimate;
5389
5390         /* check for the end of the game */
5391         if (currentMove >= forwardMostMove) {
5392           gameMode = PlayFromGameFile;
5393           ModeHighlight();
5394           SetTrainingModeOff();
5395           DisplayInformation(_("End of game"));
5396         }
5397       } else {
5398         DisplayError(_("Incorrect move"), 0);
5399       }
5400       return 1;
5401     }
5402
5403   /* Ok, now we know that the move is good, so we can kill
5404      the previous line in Analysis Mode */
5405   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5406                                 && currentMove < forwardMostMove) {
5407     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5408   }
5409
5410   /* If we need the chess program but it's dead, restart it */
5411   ResurrectChessProgram();
5412
5413   /* A user move restarts a paused game*/
5414   if (pausing)
5415     PauseEvent();
5416
5417   thinkOutput[0] = NULLCHAR;
5418
5419   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5420
5421   if (gameMode == BeginningOfGame) {
5422     if (appData.noChessProgram) {
5423       gameMode = EditGame;
5424       SetGameInfo();
5425     } else {
5426       char buf[MSG_SIZ];
5427       gameMode = MachinePlaysBlack;
5428       StartClocks();
5429       SetGameInfo();
5430       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5431       DisplayTitle(buf);
5432       if (first.sendName) {
5433         sprintf(buf, "name %s\n", gameInfo.white);
5434         SendToProgram(buf, &first);
5435       }
5436       StartClocks();
5437     }
5438     ModeHighlight();
5439   }
5440
5441   /* Relay move to ICS or chess engine */
5442   if (appData.icsActive) {
5443     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5444         gameMode == IcsExamining) {
5445       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5446       ics_user_moved = 1;
5447     }
5448   } else {
5449     if (first.sendTime && (gameMode == BeginningOfGame ||
5450                            gameMode == MachinePlaysWhite ||
5451                            gameMode == MachinePlaysBlack)) {
5452       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5453     }
5454     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5455          // [HGM] book: if program might be playing, let it use book
5456         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5457         first.maybeThinking = TRUE;
5458     } else SendMoveToProgram(forwardMostMove-1, &first);
5459     if (currentMove == cmailOldMove + 1) {
5460       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5461     }
5462   }
5463
5464   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5465
5466   switch (gameMode) {
5467   case EditGame:
5468     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5469     case MT_NONE:
5470     case MT_CHECK:
5471       break;
5472     case MT_CHECKMATE:
5473     case MT_STAINMATE:
5474       if (WhiteOnMove(currentMove)) {
5475         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5476       } else {
5477         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5478       }
5479       break;
5480     case MT_STALEMATE:
5481       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5482       break;
5483     }
5484     break;
5485     
5486   case MachinePlaysBlack:
5487   case MachinePlaysWhite:
5488     /* disable certain menu options while machine is thinking */
5489     SetMachineThinkingEnables();
5490     break;
5491
5492   default:
5493     break;
5494   }
5495
5496   if(bookHit) { // [HGM] book: simulate book reply
5497         static char bookMove[MSG_SIZ]; // a bit generous?
5498
5499         programStats.nodes = programStats.depth = programStats.time = 
5500         programStats.score = programStats.got_only_move = 0;
5501         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5502
5503         strcpy(bookMove, "move ");
5504         strcat(bookMove, bookHit);
5505         HandleMachineMove(bookMove, &first);
5506   }
5507   return 1;
5508 }
5509
5510 void
5511 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5512      int fromX, fromY, toX, toY;
5513      int promoChar;
5514 {
5515     /* [HGM] This routine was added to allow calling of its two logical
5516        parts from other modules in the old way. Before, UserMoveEvent()
5517        automatically called FinishMove() if the move was OK, and returned
5518        otherwise. I separated the two, in order to make it possible to
5519        slip a promotion popup in between. But that it always needs two
5520        calls, to the first part, (now called UserMoveTest() ), and to
5521        FinishMove if the first part succeeded. Calls that do not need
5522        to do anything in between, can call this routine the old way. 
5523     */
5524     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5525 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5526     if(moveType == AmbiguousMove)
5527         DrawPosition(FALSE, boards[currentMove]);
5528     else if(moveType != ImpossibleMove && moveType != Comment)
5529         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5530 }
5531
5532 void LeftClick(ClickType clickType, int xPix, int yPix)
5533 {
5534     int x, y;
5535     Boolean saveAnimate;
5536     static int second = 0, promotionChoice = 0;
5537     char promoChoice = NULLCHAR;
5538
5539     if (clickType == Press) ErrorPopDown();
5540
5541     x = EventToSquare(xPix, BOARD_WIDTH);
5542     y = EventToSquare(yPix, BOARD_HEIGHT);
5543     if (!flipView && y >= 0) {
5544         y = BOARD_HEIGHT - 1 - y;
5545     }
5546     if (flipView && x >= 0) {
5547         x = BOARD_WIDTH - 1 - x;
5548     }
5549
5550     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5551         if(clickType == Release) return; // ignore upclick of click-click destination
5552         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5553         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5554         if(gameInfo.holdingsWidth && 
5555                 (WhiteOnMove(currentMove) 
5556                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5557                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5558             // click in right holdings, for determining promotion piece
5559             ChessSquare p = boards[currentMove][y][x];
5560             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5561             if(p != EmptySquare) {
5562                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5563                 fromX = fromY = -1;
5564                 return;
5565             }
5566         }
5567         DrawPosition(FALSE, boards[currentMove]);
5568         return;
5569     }
5570
5571     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5572     if(clickType == Press
5573             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5574               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5575               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5576         return;
5577
5578     if (fromX == -1) {
5579         if (clickType == Press) {
5580             /* First square */
5581             if (OKToStartUserMove(x, y)) {
5582                 fromX = x;
5583                 fromY = y;
5584                 second = 0;
5585                 DragPieceBegin(xPix, yPix);
5586                 if (appData.highlightDragging) {
5587                     SetHighlights(x, y, -1, -1);
5588                 }
5589             }
5590         }
5591         return;
5592     }
5593
5594     /* fromX != -1 */
5595     if (clickType == Press && gameMode != EditPosition) {
5596         ChessSquare fromP;
5597         ChessSquare toP;
5598         int frc;
5599
5600         // ignore off-board to clicks
5601         if(y < 0 || x < 0) return;
5602
5603         /* Check if clicking again on the same color piece */
5604         fromP = boards[currentMove][fromY][fromX];
5605         toP = boards[currentMove][y][x];
5606         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5607         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5608              WhitePawn <= toP && toP <= WhiteKing &&
5609              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5610              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5611             (BlackPawn <= fromP && fromP <= BlackKing && 
5612              BlackPawn <= toP && toP <= BlackKing &&
5613              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5614              !(fromP == BlackKing && toP == BlackRook && frc))) {
5615             /* Clicked again on same color piece -- changed his mind */
5616             second = (x == fromX && y == fromY);
5617             if (appData.highlightDragging) {
5618                 SetHighlights(x, y, -1, -1);
5619             } else {
5620                 ClearHighlights();
5621             }
5622             if (OKToStartUserMove(x, y)) {
5623                 fromX = x;
5624                 fromY = y;
5625                 DragPieceBegin(xPix, yPix);
5626             }
5627             return;
5628         }
5629         // ignore clicks on holdings
5630         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5631     }
5632
5633     if (clickType == Release && x == fromX && y == fromY) {
5634         DragPieceEnd(xPix, yPix);
5635         if (appData.animateDragging) {
5636             /* Undo animation damage if any */
5637             DrawPosition(FALSE, NULL);
5638         }
5639         if (second) {
5640             /* Second up/down in same square; just abort move */
5641             second = 0;
5642             fromX = fromY = -1;
5643             ClearHighlights();
5644             gotPremove = 0;
5645             ClearPremoveHighlights();
5646         } else {
5647             /* First upclick in same square; start click-click mode */
5648             SetHighlights(x, y, -1, -1);
5649         }
5650         return;
5651     }
5652
5653     /* we now have a different from- and (possibly off-board) to-square */
5654     /* Completed move */
5655     toX = x;
5656     toY = y;
5657     saveAnimate = appData.animate;
5658     if (clickType == Press) {
5659         /* Finish clickclick move */
5660         if (appData.animate || appData.highlightLastMove) {
5661             SetHighlights(fromX, fromY, toX, toY);
5662         } else {
5663             ClearHighlights();
5664         }
5665     } else {
5666         /* Finish drag move */
5667         if (appData.highlightLastMove) {
5668             SetHighlights(fromX, fromY, toX, toY);
5669         } else {
5670             ClearHighlights();
5671         }
5672         DragPieceEnd(xPix, yPix);
5673         /* Don't animate move and drag both */
5674         appData.animate = FALSE;
5675     }
5676
5677     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5678     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5679         ClearHighlights();
5680         fromX = fromY = -1;
5681         DrawPosition(TRUE, NULL);
5682         return;
5683     }
5684
5685     // off-board moves should not be highlighted
5686     if(x < 0 || x < 0) ClearHighlights();
5687
5688     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5689         SetHighlights(fromX, fromY, toX, toY);
5690         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5691             // [HGM] super: promotion to captured piece selected from holdings
5692             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5693             promotionChoice = TRUE;
5694             // kludge follows to temporarily execute move on display, without promoting yet
5695             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5696             boards[currentMove][toY][toX] = p;
5697             DrawPosition(FALSE, boards[currentMove]);
5698             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5699             boards[currentMove][toY][toX] = q;
5700             DisplayMessage("Click in holdings to choose piece", "");
5701             return;
5702         }
5703         PromotionPopUp();
5704     } else {
5705         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5706         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5707         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5708         fromX = fromY = -1;
5709     }
5710     appData.animate = saveAnimate;
5711     if (appData.animate || appData.animateDragging) {
5712         /* Undo animation damage if needed */
5713         DrawPosition(FALSE, NULL);
5714     }
5715 }
5716
5717 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5718 {
5719 //    char * hint = lastHint;
5720     FrontEndProgramStats stats;
5721
5722     stats.which = cps == &first ? 0 : 1;
5723     stats.depth = cpstats->depth;
5724     stats.nodes = cpstats->nodes;
5725     stats.score = cpstats->score;
5726     stats.time = cpstats->time;
5727     stats.pv = cpstats->movelist;
5728     stats.hint = lastHint;
5729     stats.an_move_index = 0;
5730     stats.an_move_count = 0;
5731
5732     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5733         stats.hint = cpstats->move_name;
5734         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5735         stats.an_move_count = cpstats->nr_moves;
5736     }
5737
5738     SetProgramStats( &stats );
5739 }
5740
5741 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5742 {   // [HGM] book: this routine intercepts moves to simulate book replies
5743     char *bookHit = NULL;
5744
5745     //first determine if the incoming move brings opponent into his book
5746     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5747         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5748     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5749     if(bookHit != NULL && !cps->bookSuspend) {
5750         // make sure opponent is not going to reply after receiving move to book position
5751         SendToProgram("force\n", cps);
5752         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5753     }
5754     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5755     // now arrange restart after book miss
5756     if(bookHit) {
5757         // after a book hit we never send 'go', and the code after the call to this routine
5758         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5759         char buf[MSG_SIZ];
5760         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5761         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5762         SendToProgram(buf, cps);
5763         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5764     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5765         SendToProgram("go\n", cps);
5766         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5767     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5768         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5769             SendToProgram("go\n", cps); 
5770         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5771     }
5772     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5773 }
5774
5775 char *savedMessage;
5776 ChessProgramState *savedState;
5777 void DeferredBookMove(void)
5778 {
5779         if(savedState->lastPing != savedState->lastPong)
5780                     ScheduleDelayedEvent(DeferredBookMove, 10);
5781         else
5782         HandleMachineMove(savedMessage, savedState);
5783 }
5784
5785 void
5786 HandleMachineMove(message, cps)
5787      char *message;
5788      ChessProgramState *cps;
5789 {
5790     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5791     char realname[MSG_SIZ];
5792     int fromX, fromY, toX, toY;
5793     ChessMove moveType;
5794     char promoChar;
5795     char *p;
5796     int machineWhite;
5797     char *bookHit;
5798
5799 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5800     /*
5801      * Kludge to ignore BEL characters
5802      */
5803     while (*message == '\007') message++;
5804
5805     /*
5806      * [HGM] engine debug message: ignore lines starting with '#' character
5807      */
5808     if(cps->debug && *message == '#') return;
5809
5810     /*
5811      * Look for book output
5812      */
5813     if (cps == &first && bookRequested) {
5814         if (message[0] == '\t' || message[0] == ' ') {
5815             /* Part of the book output is here; append it */
5816             strcat(bookOutput, message);
5817             strcat(bookOutput, "  \n");
5818             return;
5819         } else if (bookOutput[0] != NULLCHAR) {
5820             /* All of book output has arrived; display it */
5821             char *p = bookOutput;
5822             while (*p != NULLCHAR) {
5823                 if (*p == '\t') *p = ' ';
5824                 p++;
5825             }
5826             DisplayInformation(bookOutput);
5827             bookRequested = FALSE;
5828             /* Fall through to parse the current output */
5829         }
5830     }
5831
5832     /*
5833      * Look for machine move.
5834      */
5835     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5836         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5837     {
5838         /* This method is only useful on engines that support ping */
5839         if (cps->lastPing != cps->lastPong) {
5840           if (gameMode == BeginningOfGame) {
5841             /* Extra move from before last new; ignore */
5842             if (appData.debugMode) {
5843                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5844             }
5845           } else {
5846             if (appData.debugMode) {
5847                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5848                         cps->which, gameMode);
5849             }
5850
5851             SendToProgram("undo\n", cps);
5852           }
5853           return;
5854         }
5855
5856         switch (gameMode) {
5857           case BeginningOfGame:
5858             /* Extra move from before last reset; ignore */
5859             if (appData.debugMode) {
5860                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5861             }
5862             return;
5863
5864           case EndOfGame:
5865           case IcsIdle:
5866           default:
5867             /* Extra move after we tried to stop.  The mode test is
5868                not a reliable way of detecting this problem, but it's
5869                the best we can do on engines that don't support ping.
5870             */
5871             if (appData.debugMode) {
5872                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5873                         cps->which, gameMode);
5874             }
5875             SendToProgram("undo\n", cps);
5876             return;
5877
5878           case MachinePlaysWhite:
5879           case IcsPlayingWhite:
5880             machineWhite = TRUE;
5881             break;
5882
5883           case MachinePlaysBlack:
5884           case IcsPlayingBlack:
5885             machineWhite = FALSE;
5886             break;
5887
5888           case TwoMachinesPlay:
5889             machineWhite = (cps->twoMachinesColor[0] == 'w');
5890             break;
5891         }
5892         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5893             if (appData.debugMode) {
5894                 fprintf(debugFP,
5895                         "Ignoring move out of turn by %s, gameMode %d"
5896                         ", forwardMost %d\n",
5897                         cps->which, gameMode, forwardMostMove);
5898             }
5899             return;
5900         }
5901
5902     if (appData.debugMode) { int f = forwardMostMove;
5903         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5904                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5905                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5906     }
5907         if(cps->alphaRank) AlphaRank(machineMove, 4);
5908         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5909                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5910             /* Machine move could not be parsed; ignore it. */
5911             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5912                     machineMove, cps->which);
5913             DisplayError(buf1, 0);
5914             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5915                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5916             if (gameMode == TwoMachinesPlay) {
5917               GameEnds(machineWhite ? BlackWins : WhiteWins,
5918                        buf1, GE_XBOARD);
5919             }
5920             return;
5921         }
5922
5923         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5924         /* So we have to redo legality test with true e.p. status here,  */
5925         /* to make sure an illegal e.p. capture does not slip through,   */
5926         /* to cause a forfeit on a justified illegal-move complaint      */
5927         /* of the opponent.                                              */
5928         if( gameMode==TwoMachinesPlay && appData.testLegality
5929             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5930                                                               ) {
5931            ChessMove moveType;
5932            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5933                              fromY, fromX, toY, toX, promoChar);
5934             if (appData.debugMode) {
5935                 int i;
5936                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5937                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5938                 fprintf(debugFP, "castling rights\n");
5939             }
5940             if(moveType == IllegalMove) {
5941                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5942                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5943                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5944                            buf1, GE_XBOARD);
5945                 return;
5946            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5947            /* [HGM] Kludge to handle engines that send FRC-style castling
5948               when they shouldn't (like TSCP-Gothic) */
5949            switch(moveType) {
5950              case WhiteASideCastleFR:
5951              case BlackASideCastleFR:
5952                toX+=2;
5953                currentMoveString[2]++;
5954                break;
5955              case WhiteHSideCastleFR:
5956              case BlackHSideCastleFR:
5957                toX--;
5958                currentMoveString[2]--;
5959                break;
5960              default: ; // nothing to do, but suppresses warning of pedantic compilers
5961            }
5962         }
5963         hintRequested = FALSE;
5964         lastHint[0] = NULLCHAR;
5965         bookRequested = FALSE;
5966         /* Program may be pondering now */
5967         cps->maybeThinking = TRUE;
5968         if (cps->sendTime == 2) cps->sendTime = 1;
5969         if (cps->offeredDraw) cps->offeredDraw--;
5970
5971 #if ZIPPY
5972         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5973             first.initDone) {
5974           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5975           ics_user_moved = 1;
5976           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5977                 char buf[3*MSG_SIZ];
5978
5979                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5980                         programStats.score / 100.,
5981                         programStats.depth,
5982                         programStats.time / 100.,
5983                         (unsigned int)programStats.nodes,
5984                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5985                         programStats.movelist);
5986                 SendToICS(buf);
5987 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5988           }
5989         }
5990 #endif
5991         /* currentMoveString is set as a side-effect of ParseOneMove */
5992         strcpy(machineMove, currentMoveString);
5993         strcat(machineMove, "\n");
5994         strcpy(moveList[forwardMostMove], machineMove);
5995
5996         /* [AS] Save move info and clear stats for next move */
5997         pvInfoList[ forwardMostMove ].score = programStats.score;
5998         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5999         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6000         ClearProgramStats();
6001         thinkOutput[0] = NULLCHAR;
6002         hiddenThinkOutputState = 0;
6003
6004         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6005
6006         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6007         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6008             int count = 0;
6009
6010             while( count < adjudicateLossPlies ) {
6011                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6012
6013                 if( count & 1 ) {
6014                     score = -score; /* Flip score for winning side */
6015                 }
6016
6017                 if( score > adjudicateLossThreshold ) {
6018                     break;
6019                 }
6020
6021                 count++;
6022             }
6023
6024             if( count >= adjudicateLossPlies ) {
6025                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026
6027                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6028                     "Xboard adjudication", 
6029                     GE_XBOARD );
6030
6031                 return;
6032             }
6033         }
6034
6035         if( gameMode == TwoMachinesPlay ) {
6036           // [HGM] some adjudications useful with buggy engines
6037             int k, count = 0; static int bare = 1;
6038           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6039
6040
6041             if( appData.testLegality )
6042             {   /* [HGM] Some more adjudications for obstinate engines */
6043                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6044                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6045                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6046                 static int moveCount = 6;
6047                 ChessMove result;
6048                 char *reason = NULL;
6049
6050                 /* Count what is on board. */
6051                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6052                 {   ChessSquare p = boards[forwardMostMove][i][j];
6053                     int m=i;
6054
6055                     switch((int) p)
6056                     {   /* count B,N,R and other of each side */
6057                         case WhiteKing:
6058                         case BlackKing:
6059                              NrK++; break; // [HGM] atomic: count Kings
6060                         case WhiteKnight:
6061                              NrWN++; break;
6062                         case WhiteBishop:
6063                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6064                              bishopsColor |= 1 << ((i^j)&1);
6065                              NrWB++; break;
6066                         case BlackKnight:
6067                              NrBN++; break;
6068                         case BlackBishop:
6069                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6070                              bishopsColor |= 1 << ((i^j)&1);
6071                              NrBB++; break;
6072                         case WhiteRook:
6073                              NrWR++; break;
6074                         case BlackRook:
6075                              NrBR++; break;
6076                         case WhiteQueen:
6077                              NrWQ++; break;
6078                         case BlackQueen:
6079                              NrBQ++; break;
6080                         case EmptySquare: 
6081                              break;
6082                         case BlackPawn:
6083                              m = 7-i;
6084                         case WhitePawn:
6085                              PawnAdvance += m; NrPawns++;
6086                     }
6087                     NrPieces += (p != EmptySquare);
6088                     NrW += ((int)p < (int)BlackPawn);
6089                     if(gameInfo.variant == VariantXiangqi && 
6090                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6091                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6092                         NrW -= ((int)p < (int)BlackPawn);
6093                     }
6094                 }
6095
6096                 /* Some material-based adjudications that have to be made before stalemate test */
6097                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6098                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6099                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6100                      if(appData.checkMates) {
6101                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6102                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6103                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6104                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6105                          return;
6106                      }
6107                 }
6108
6109                 /* Bare King in Shatranj (loses) or Losers (wins) */
6110                 if( NrW == 1 || NrPieces - NrW == 1) {
6111                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6112                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6113                      if(appData.checkMates) {
6114                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6115                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6117                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6118                          return;
6119                      }
6120                   } else
6121                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6122                   {    /* bare King */
6123                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6124                         if(appData.checkMates) {
6125                             /* but only adjudicate if adjudication enabled */
6126                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6127                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6128                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6129                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6130                             return;
6131                         }
6132                   }
6133                 } else bare = 1;
6134
6135
6136             // don't wait for engine to announce game end if we can judge ourselves
6137             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6138               case MT_CHECK:
6139                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6140                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6141                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6142                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6143                             checkCnt++;
6144                         if(checkCnt >= 2) {
6145                             reason = "Xboard adjudication: 3rd check";
6146                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6147                             break;
6148                         }
6149                     }
6150                 }
6151               case MT_NONE:
6152               default:
6153                 break;
6154               case MT_STALEMATE:
6155               case MT_STAINMATE:
6156                 reason = "Xboard adjudication: Stalemate";
6157                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6158                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6159                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6160                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6161                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6162                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6163                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6164                                                                         EP_CHECKMATE : EP_WINS);
6165                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6166                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6167                 }
6168                 break;
6169               case MT_CHECKMATE:
6170                 reason = "Xboard adjudication: Checkmate";
6171                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6172                 break;
6173             }
6174
6175                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6176                     case EP_STALEMATE:
6177                         result = GameIsDrawn; break;
6178                     case EP_CHECKMATE:
6179                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6180                     case EP_WINS:
6181                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6182                     default:
6183                         result = (ChessMove) 0;
6184                 }
6185                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6186                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6187                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6188                     GameEnds( result, reason, GE_XBOARD );
6189                     return;
6190                 }
6191
6192                 /* Next absolutely insufficient mating material. */
6193                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6194                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6195                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6196                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6197                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6198
6199                      /* always flag draws, for judging claims */
6200                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6201
6202                      if(appData.materialDraws) {
6203                          /* but only adjudicate them if adjudication enabled */
6204                          SendToProgram("force\n", cps->other); // suppress reply
6205                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6206                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6207                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6208                          return;
6209                      }
6210                 }
6211
6212                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6213                 if(NrPieces == 4 && 
6214                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6215                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6216                    || NrWN==2 || NrBN==2     /* KNNK */
6217                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6218                   ) ) {
6219                      if(--moveCount < 0 && appData.trivialDraws)
6220                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6221                           SendToProgram("force\n", cps->other); // suppress reply
6222                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6223                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6224                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6225                           return;
6226                      }
6227                 } else moveCount = 6;
6228             }
6229           }
6230           
6231           if (appData.debugMode) { int i;
6232             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6233                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6234                     appData.drawRepeats);
6235             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6236               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6237             
6238           }
6239
6240                 /* Check for rep-draws */
6241                 count = 0;
6242                 for(k = forwardMostMove-2;
6243                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6244                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6245                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6246                     k-=2)
6247                 {   int rights=0;
6248                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6249                         /* compare castling rights */
6250                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6251                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6252                                 rights++; /* King lost rights, while rook still had them */
6253                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6254                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6255                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6256                                    rights++; /* but at least one rook lost them */
6257                         }
6258                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6259                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6260                                 rights++; 
6261                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6262                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6263                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6264                                    rights++;
6265                         }
6266                         if( rights == 0 && ++count > appData.drawRepeats-2
6267                             && appData.drawRepeats > 1) {
6268                              /* adjudicate after user-specified nr of repeats */
6269                              SendToProgram("force\n", cps->other); // suppress reply
6270                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6271                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6272                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6273                                 // [HGM] xiangqi: check for forbidden perpetuals
6274                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6275                                 for(m=forwardMostMove; m>k; m-=2) {
6276                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6277                                         ourPerpetual = 0; // the current mover did not always check
6278                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6279                                         hisPerpetual = 0; // the opponent did not always check
6280                                 }
6281                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6282                                                                         ourPerpetual, hisPerpetual);
6283                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6284                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6285                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6286                                     return;
6287                                 }
6288                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6289                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6290                                 // Now check for perpetual chases
6291                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6292                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6293                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6294                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6295                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6296                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6297                                         return;
6298                                     }
6299                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6300                                         break; // Abort repetition-checking loop.
6301                                 }
6302                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6303                              }
6304                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6305                              return;
6306                         }
6307                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6308                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6309                     }
6310                 }
6311
6312                 /* Now we test for 50-move draws. Determine ply count */
6313                 count = forwardMostMove;
6314                 /* look for last irreversble move */
6315                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6316                     count--;
6317                 /* if we hit starting position, add initial plies */
6318                 if( count == backwardMostMove )
6319                     count -= initialRulePlies;
6320                 count = forwardMostMove - count; 
6321                 if( count >= 100)
6322                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6323                          /* this is used to judge if draw claims are legal */
6324                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6325                          SendToProgram("force\n", cps->other); // suppress reply
6326                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6327                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6328                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6329                          return;
6330                 }
6331
6332                 /* if draw offer is pending, treat it as a draw claim
6333                  * when draw condition present, to allow engines a way to
6334                  * claim draws before making their move to avoid a race
6335                  * condition occurring after their move
6336                  */
6337                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6338                          char *p = NULL;
6339                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6340                              p = "Draw claim: 50-move rule";
6341                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6342                              p = "Draw claim: 3-fold repetition";
6343                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6344                              p = "Draw claim: insufficient mating material";
6345                          if( p != NULL ) {
6346                              SendToProgram("force\n", cps->other); // suppress reply
6347                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6348                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6349                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350                              return;
6351                          }
6352                 }
6353
6354
6355                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6356                     SendToProgram("force\n", cps->other); // suppress reply
6357                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6358                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6359
6360                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6361
6362                     return;
6363                 }
6364         }
6365
6366         bookHit = NULL;
6367         if (gameMode == TwoMachinesPlay) {
6368             /* [HGM] relaying draw offers moved to after reception of move */
6369             /* and interpreting offer as claim if it brings draw condition */
6370             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6371                 SendToProgram("draw\n", cps->other);
6372             }
6373             if (cps->other->sendTime) {
6374                 SendTimeRemaining(cps->other,
6375                                   cps->other->twoMachinesColor[0] == 'w');
6376             }
6377             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6378             if (firstMove && !bookHit) {
6379                 firstMove = FALSE;
6380                 if (cps->other->useColors) {
6381                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6382                 }
6383                 SendToProgram("go\n", cps->other);
6384             }
6385             cps->other->maybeThinking = TRUE;
6386         }
6387
6388         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6389         
6390         if (!pausing && appData.ringBellAfterMoves) {
6391             RingBell();
6392         }
6393
6394         /* 
6395          * Reenable menu items that were disabled while
6396          * machine was thinking
6397          */
6398         if (gameMode != TwoMachinesPlay)
6399             SetUserThinkingEnables();
6400
6401         // [HGM] book: after book hit opponent has received move and is now in force mode
6402         // force the book reply into it, and then fake that it outputted this move by jumping
6403         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6404         if(bookHit) {
6405                 static char bookMove[MSG_SIZ]; // a bit generous?
6406
6407                 strcpy(bookMove, "move ");
6408                 strcat(bookMove, bookHit);
6409                 message = bookMove;
6410                 cps = cps->other;
6411                 programStats.nodes = programStats.depth = programStats.time = 
6412                 programStats.score = programStats.got_only_move = 0;
6413                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6414
6415                 if(cps->lastPing != cps->lastPong) {
6416                     savedMessage = message; // args for deferred call
6417                     savedState = cps;
6418                     ScheduleDelayedEvent(DeferredBookMove, 10);
6419                     return;
6420                 }
6421                 goto FakeBookMove;
6422         }
6423
6424         return;
6425     }
6426
6427     /* Set special modes for chess engines.  Later something general
6428      *  could be added here; for now there is just one kludge feature,
6429      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6430      *  when "xboard" is given as an interactive command.
6431      */
6432     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6433         cps->useSigint = FALSE;
6434         cps->useSigterm = FALSE;
6435     }
6436     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6437       ParseFeatures(message+8, cps);
6438       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6439     }
6440
6441     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6442      * want this, I was asked to put it in, and obliged.
6443      */
6444     if (!strncmp(message, "setboard ", 9)) {
6445         Board initial_position;
6446
6447         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6448
6449         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6450             DisplayError(_("Bad FEN received from engine"), 0);
6451             return ;
6452         } else {
6453            Reset(TRUE, FALSE);
6454            CopyBoard(boards[0], initial_position);
6455            initialRulePlies = FENrulePlies;
6456            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6457            else gameMode = MachinePlaysBlack;                 
6458            DrawPosition(FALSE, boards[currentMove]);
6459         }
6460         return;
6461     }
6462
6463     /*
6464      * Look for communication commands
6465      */
6466     if (!strncmp(message, "telluser ", 9)) {
6467         DisplayNote(message + 9);
6468         return;
6469     }
6470     if (!strncmp(message, "tellusererror ", 14)) {
6471         DisplayError(message + 14, 0);
6472         return;
6473     }
6474     if (!strncmp(message, "tellopponent ", 13)) {
6475       if (appData.icsActive) {
6476         if (loggedOn) {
6477           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6478           SendToICS(buf1);
6479         }
6480       } else {
6481         DisplayNote(message + 13);
6482       }
6483       return;
6484     }
6485     if (!strncmp(message, "tellothers ", 11)) {
6486       if (appData.icsActive) {
6487         if (loggedOn) {
6488           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6489           SendToICS(buf1);
6490         }
6491       }
6492       return;
6493     }
6494     if (!strncmp(message, "tellall ", 8)) {
6495       if (appData.icsActive) {
6496         if (loggedOn) {
6497           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6498           SendToICS(buf1);
6499         }
6500       } else {
6501         DisplayNote(message + 8);
6502       }
6503       return;
6504     }
6505     if (strncmp(message, "warning", 7) == 0) {
6506         /* Undocumented feature, use tellusererror in new code */
6507         DisplayError(message, 0);
6508         return;
6509     }
6510     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6511         strcpy(realname, cps->tidy);
6512         strcat(realname, " query");
6513         AskQuestion(realname, buf2, buf1, cps->pr);
6514         return;
6515     }
6516     /* Commands from the engine directly to ICS.  We don't allow these to be 
6517      *  sent until we are logged on. Crafty kibitzes have been known to 
6518      *  interfere with the login process.
6519      */
6520     if (loggedOn) {
6521         if (!strncmp(message, "tellics ", 8)) {
6522             SendToICS(message + 8);
6523             SendToICS("\n");
6524             return;
6525         }
6526         if (!strncmp(message, "tellicsnoalias ", 15)) {
6527             SendToICS(ics_prefix);
6528             SendToICS(message + 15);
6529             SendToICS("\n");
6530             return;
6531         }
6532         /* The following are for backward compatibility only */
6533         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6534             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6535             SendToICS(ics_prefix);
6536             SendToICS(message);
6537             SendToICS("\n");
6538             return;
6539         }
6540     }
6541     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6542         return;
6543     }
6544     /*
6545      * If the move is illegal, cancel it and redraw the board.
6546      * Also deal with other error cases.  Matching is rather loose
6547      * here to accommodate engines written before the spec.
6548      */
6549     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6550         strncmp(message, "Error", 5) == 0) {
6551         if (StrStr(message, "name") || 
6552             StrStr(message, "rating") || StrStr(message, "?") ||
6553             StrStr(message, "result") || StrStr(message, "board") ||
6554             StrStr(message, "bk") || StrStr(message, "computer") ||
6555             StrStr(message, "variant") || StrStr(message, "hint") ||
6556             StrStr(message, "random") || StrStr(message, "depth") ||
6557             StrStr(message, "accepted")) {
6558             return;
6559         }
6560         if (StrStr(message, "protover")) {
6561           /* Program is responding to input, so it's apparently done
6562              initializing, and this error message indicates it is
6563              protocol version 1.  So we don't need to wait any longer
6564              for it to initialize and send feature commands. */
6565           FeatureDone(cps, 1);
6566           cps->protocolVersion = 1;
6567           return;
6568         }
6569         cps->maybeThinking = FALSE;
6570
6571         if (StrStr(message, "draw")) {
6572             /* Program doesn't have "draw" command */
6573             cps->sendDrawOffers = 0;
6574             return;
6575         }
6576         if (cps->sendTime != 1 &&
6577             (StrStr(message, "time") || StrStr(message, "otim"))) {
6578           /* Program apparently doesn't have "time" or "otim" command */
6579           cps->sendTime = 0;
6580           return;
6581         }
6582         if (StrStr(message, "analyze")) {
6583             cps->analysisSupport = FALSE;
6584             cps->analyzing = FALSE;
6585             Reset(FALSE, TRUE);
6586             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6587             DisplayError(buf2, 0);
6588             return;
6589         }
6590         if (StrStr(message, "(no matching move)st")) {
6591           /* Special kludge for GNU Chess 4 only */
6592           cps->stKludge = TRUE;
6593           SendTimeControl(cps, movesPerSession, timeControl,
6594                           timeIncrement, appData.searchDepth,
6595                           searchTime);
6596           return;
6597         }
6598         if (StrStr(message, "(no matching move)sd")) {
6599           /* Special kludge for GNU Chess 4 only */
6600           cps->sdKludge = TRUE;
6601           SendTimeControl(cps, movesPerSession, timeControl,
6602                           timeIncrement, appData.searchDepth,
6603                           searchTime);
6604           return;
6605         }
6606         if (!StrStr(message, "llegal")) {
6607             return;
6608         }
6609         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6610             gameMode == IcsIdle) return;
6611         if (forwardMostMove <= backwardMostMove) return;
6612         if (pausing) PauseEvent();
6613       if(appData.forceIllegal) {
6614             // [HGM] illegal: machine refused move; force position after move into it
6615           SendToProgram("force\n", cps);
6616           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6617                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6618                 // when black is to move, while there might be nothing on a2 or black
6619                 // might already have the move. So send the board as if white has the move.
6620                 // But first we must change the stm of the engine, as it refused the last move
6621                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6622                 if(WhiteOnMove(forwardMostMove)) {
6623                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6624                     SendBoard(cps, forwardMostMove); // kludgeless board
6625                 } else {
6626                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6627                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6628                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6629                 }
6630           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6631             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6632                  gameMode == TwoMachinesPlay)
6633               SendToProgram("go\n", cps);
6634             return;
6635       } else
6636         if (gameMode == PlayFromGameFile) {
6637             /* Stop reading this game file */
6638             gameMode = EditGame;
6639             ModeHighlight();
6640         }
6641         currentMove = --forwardMostMove;
6642         DisplayMove(currentMove-1); /* before DisplayMoveError */
6643         SwitchClocks();
6644         DisplayBothClocks();
6645         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6646                 parseList[currentMove], cps->which);
6647         DisplayMoveError(buf1);
6648         DrawPosition(FALSE, boards[currentMove]);
6649
6650         /* [HGM] illegal-move claim should forfeit game when Xboard */
6651         /* only passes fully legal moves                            */
6652         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6653             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6654                                 "False illegal-move claim", GE_XBOARD );
6655         }
6656         return;
6657     }
6658     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6659         /* Program has a broken "time" command that
6660            outputs a string not ending in newline.
6661            Don't use it. */
6662         cps->sendTime = 0;
6663     }
6664     
6665     /*
6666      * If chess program startup fails, exit with an error message.
6667      * Attempts to recover here are futile.
6668      */
6669     if ((StrStr(message, "unknown host") != NULL)
6670         || (StrStr(message, "No remote directory") != NULL)
6671         || (StrStr(message, "not found") != NULL)
6672         || (StrStr(message, "No such file") != NULL)
6673         || (StrStr(message, "can't alloc") != NULL)
6674         || (StrStr(message, "Permission denied") != NULL)) {
6675
6676         cps->maybeThinking = FALSE;
6677         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6678                 cps->which, cps->program, cps->host, message);
6679         RemoveInputSource(cps->isr);
6680         DisplayFatalError(buf1, 0, 1);
6681         return;
6682     }
6683     
6684     /* 
6685      * Look for hint output
6686      */
6687     if (sscanf(message, "Hint: %s", buf1) == 1) {
6688         if (cps == &first && hintRequested) {
6689             hintRequested = FALSE;
6690             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6691                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6692                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6693                                     PosFlags(forwardMostMove),
6694                                     fromY, fromX, toY, toX, promoChar, buf1);
6695                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6696                 DisplayInformation(buf2);
6697             } else {
6698                 /* Hint move could not be parsed!? */
6699               snprintf(buf2, sizeof(buf2),
6700                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6701                         buf1, cps->which);
6702                 DisplayError(buf2, 0);
6703             }
6704         } else {
6705             strcpy(lastHint, buf1);
6706         }
6707         return;
6708     }
6709
6710     /*
6711      * Ignore other messages if game is not in progress
6712      */
6713     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6714         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6715
6716     /*
6717      * look for win, lose, draw, or draw offer
6718      */
6719     if (strncmp(message, "1-0", 3) == 0) {
6720         char *p, *q, *r = "";
6721         p = strchr(message, '{');
6722         if (p) {
6723             q = strchr(p, '}');
6724             if (q) {
6725                 *q = NULLCHAR;
6726                 r = p + 1;
6727             }
6728         }
6729         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6730         return;
6731     } else if (strncmp(message, "0-1", 3) == 0) {
6732         char *p, *q, *r = "";
6733         p = strchr(message, '{');
6734         if (p) {
6735             q = strchr(p, '}');
6736             if (q) {
6737                 *q = NULLCHAR;
6738                 r = p + 1;
6739             }
6740         }
6741         /* Kludge for Arasan 4.1 bug */
6742         if (strcmp(r, "Black resigns") == 0) {
6743             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6744             return;
6745         }
6746         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6747         return;
6748     } else if (strncmp(message, "1/2", 3) == 0) {
6749         char *p, *q, *r = "";
6750         p = strchr(message, '{');
6751         if (p) {
6752             q = strchr(p, '}');
6753             if (q) {
6754                 *q = NULLCHAR;
6755                 r = p + 1;
6756             }
6757         }
6758             
6759         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6760         return;
6761
6762     } else if (strncmp(message, "White resign", 12) == 0) {
6763         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6764         return;
6765     } else if (strncmp(message, "Black resign", 12) == 0) {
6766         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6767         return;
6768     } else if (strncmp(message, "White matches", 13) == 0 ||
6769                strncmp(message, "Black matches", 13) == 0   ) {
6770         /* [HGM] ignore GNUShogi noises */
6771         return;
6772     } else if (strncmp(message, "White", 5) == 0 &&
6773                message[5] != '(' &&
6774                StrStr(message, "Black") == NULL) {
6775         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6776         return;
6777     } else if (strncmp(message, "Black", 5) == 0 &&
6778                message[5] != '(') {
6779         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6780         return;
6781     } else if (strcmp(message, "resign") == 0 ||
6782                strcmp(message, "computer resigns") == 0) {
6783         switch (gameMode) {
6784           case MachinePlaysBlack:
6785           case IcsPlayingBlack:
6786             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6787             break;
6788           case MachinePlaysWhite:
6789           case IcsPlayingWhite:
6790             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6791             break;
6792           case TwoMachinesPlay:
6793             if (cps->twoMachinesColor[0] == 'w')
6794               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6795             else
6796               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6797             break;
6798           default:
6799             /* can't happen */
6800             break;
6801         }
6802         return;
6803     } else if (strncmp(message, "opponent mates", 14) == 0) {
6804         switch (gameMode) {
6805           case MachinePlaysBlack:
6806           case IcsPlayingBlack:
6807             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6808             break;
6809           case MachinePlaysWhite:
6810           case IcsPlayingWhite:
6811             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6812             break;
6813           case TwoMachinesPlay:
6814             if (cps->twoMachinesColor[0] == 'w')
6815               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6816             else
6817               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6818             break;
6819           default:
6820             /* can't happen */
6821             break;
6822         }
6823         return;
6824     } else if (strncmp(message, "computer mates", 14) == 0) {
6825         switch (gameMode) {
6826           case MachinePlaysBlack:
6827           case IcsPlayingBlack:
6828             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6829             break;
6830           case MachinePlaysWhite:
6831           case IcsPlayingWhite:
6832             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6833             break;
6834           case TwoMachinesPlay:
6835             if (cps->twoMachinesColor[0] == 'w')
6836               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6837             else
6838               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6839             break;
6840           default:
6841             /* can't happen */
6842             break;
6843         }
6844         return;
6845     } else if (strncmp(message, "checkmate", 9) == 0) {
6846         if (WhiteOnMove(forwardMostMove)) {
6847             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6848         } else {
6849             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6850         }
6851         return;
6852     } else if (strstr(message, "Draw") != NULL ||
6853                strstr(message, "game is a draw") != NULL) {
6854         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6855         return;
6856     } else if (strstr(message, "offer") != NULL &&
6857                strstr(message, "draw") != NULL) {
6858 #if ZIPPY
6859         if (appData.zippyPlay && first.initDone) {
6860             /* Relay offer to ICS */
6861             SendToICS(ics_prefix);
6862             SendToICS("draw\n");
6863         }
6864 #endif
6865         cps->offeredDraw = 2; /* valid until this engine moves twice */
6866         if (gameMode == TwoMachinesPlay) {
6867             if (cps->other->offeredDraw) {
6868                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6869             /* [HGM] in two-machine mode we delay relaying draw offer      */
6870             /* until after we also have move, to see if it is really claim */
6871             }
6872         } else if (gameMode == MachinePlaysWhite ||
6873                    gameMode == MachinePlaysBlack) {
6874           if (userOfferedDraw) {
6875             DisplayInformation(_("Machine accepts your draw offer"));
6876             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6877           } else {
6878             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6879           }
6880         }
6881     }
6882
6883     
6884     /*
6885      * Look for thinking output
6886      */
6887     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6888           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6889                                 ) {
6890         int plylev, mvleft, mvtot, curscore, time;
6891         char mvname[MOVE_LEN];
6892         u64 nodes; // [DM]
6893         char plyext;
6894         int ignore = FALSE;
6895         int prefixHint = FALSE;
6896         mvname[0] = NULLCHAR;
6897
6898         switch (gameMode) {
6899           case MachinePlaysBlack:
6900           case IcsPlayingBlack:
6901             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6902             break;
6903           case MachinePlaysWhite:
6904           case IcsPlayingWhite:
6905             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6906             break;
6907           case AnalyzeMode:
6908           case AnalyzeFile:
6909             break;
6910           case IcsObserving: /* [DM] icsEngineAnalyze */
6911             if (!appData.icsEngineAnalyze) ignore = TRUE;
6912             break;
6913           case TwoMachinesPlay:
6914             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6915                 ignore = TRUE;
6916             }
6917             break;
6918           default:
6919             ignore = TRUE;
6920             break;
6921         }
6922
6923         if (!ignore) {
6924             buf1[0] = NULLCHAR;
6925             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6926                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6927
6928                 if (plyext != ' ' && plyext != '\t') {
6929                     time *= 100;
6930                 }
6931
6932                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6933                 if( cps->scoreIsAbsolute && 
6934                     ( gameMode == MachinePlaysBlack ||
6935                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6936                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6937                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6938                      !WhiteOnMove(currentMove)
6939                     ) )
6940                 {
6941                     curscore = -curscore;
6942                 }
6943
6944
6945                 programStats.depth = plylev;
6946                 programStats.nodes = nodes;
6947                 programStats.time = time;
6948                 programStats.score = curscore;
6949                 programStats.got_only_move = 0;
6950
6951                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6952                         int ticklen;
6953
6954                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6955                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6956                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6957                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6958                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6959                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6960                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6961                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6962                 }
6963
6964                 /* Buffer overflow protection */
6965                 if (buf1[0] != NULLCHAR) {
6966                     if (strlen(buf1) >= sizeof(programStats.movelist)
6967                         && appData.debugMode) {
6968                         fprintf(debugFP,
6969                                 "PV is too long; using the first %u bytes.\n",
6970                                 (unsigned) sizeof(programStats.movelist) - 1);
6971                     }
6972
6973                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6974                 } else {
6975                     sprintf(programStats.movelist, " no PV\n");
6976                 }
6977
6978                 if (programStats.seen_stat) {
6979                     programStats.ok_to_send = 1;
6980                 }
6981
6982                 if (strchr(programStats.movelist, '(') != NULL) {
6983                     programStats.line_is_book = 1;
6984                     programStats.nr_moves = 0;
6985                     programStats.moves_left = 0;
6986                 } else {
6987                     programStats.line_is_book = 0;
6988                 }
6989
6990                 SendProgramStatsToFrontend( cps, &programStats );
6991
6992                 /* 
6993                     [AS] Protect the thinkOutput buffer from overflow... this
6994                     is only useful if buf1 hasn't overflowed first!
6995                 */
6996                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6997                         plylev, 
6998                         (gameMode == TwoMachinesPlay ?
6999                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7000                         ((double) curscore) / 100.0,
7001                         prefixHint ? lastHint : "",
7002                         prefixHint ? " " : "" );
7003
7004                 if( buf1[0] != NULLCHAR ) {
7005                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7006
7007                     if( strlen(buf1) > max_len ) {
7008                         if( appData.debugMode) {
7009                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7010                         }
7011                         buf1[max_len+1] = '\0';
7012                     }
7013
7014                     strcat( thinkOutput, buf1 );
7015                 }
7016
7017                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7018                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7019                     DisplayMove(currentMove - 1);
7020                 }
7021                 return;
7022
7023             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7024                 /* crafty (9.25+) says "(only move) <move>"
7025                  * if there is only 1 legal move
7026                  */
7027                 sscanf(p, "(only move) %s", buf1);
7028                 sprintf(thinkOutput, "%s (only move)", buf1);
7029                 sprintf(programStats.movelist, "%s (only move)", buf1);
7030                 programStats.depth = 1;
7031                 programStats.nr_moves = 1;
7032                 programStats.moves_left = 1;
7033                 programStats.nodes = 1;
7034                 programStats.time = 1;
7035                 programStats.got_only_move = 1;
7036
7037                 /* Not really, but we also use this member to
7038                    mean "line isn't going to change" (Crafty
7039                    isn't searching, so stats won't change) */
7040                 programStats.line_is_book = 1;
7041
7042                 SendProgramStatsToFrontend( cps, &programStats );
7043                 
7044                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7045                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7046                     DisplayMove(currentMove - 1);
7047                 }
7048                 return;
7049             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7050                               &time, &nodes, &plylev, &mvleft,
7051                               &mvtot, mvname) >= 5) {
7052                 /* The stat01: line is from Crafty (9.29+) in response
7053                    to the "." command */
7054                 programStats.seen_stat = 1;
7055                 cps->maybeThinking = TRUE;
7056
7057                 if (programStats.got_only_move || !appData.periodicUpdates)
7058                   return;
7059
7060                 programStats.depth = plylev;
7061                 programStats.time = time;
7062                 programStats.nodes = nodes;
7063                 programStats.moves_left = mvleft;
7064                 programStats.nr_moves = mvtot;
7065                 strcpy(programStats.move_name, mvname);
7066                 programStats.ok_to_send = 1;
7067                 programStats.movelist[0] = '\0';
7068
7069                 SendProgramStatsToFrontend( cps, &programStats );
7070
7071                 return;
7072
7073             } else if (strncmp(message,"++",2) == 0) {
7074                 /* Crafty 9.29+ outputs this */
7075                 programStats.got_fail = 2;
7076                 return;
7077
7078             } else if (strncmp(message,"--",2) == 0) {
7079                 /* Crafty 9.29+ outputs this */
7080                 programStats.got_fail = 1;
7081                 return;
7082
7083             } else if (thinkOutput[0] != NULLCHAR &&
7084                        strncmp(message, "    ", 4) == 0) {
7085                 unsigned message_len;
7086
7087                 p = message;
7088                 while (*p && *p == ' ') p++;
7089
7090                 message_len = strlen( p );
7091
7092                 /* [AS] Avoid buffer overflow */
7093                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7094                     strcat(thinkOutput, " ");
7095                     strcat(thinkOutput, p);
7096                 }
7097
7098                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7099                     strcat(programStats.movelist, " ");
7100                     strcat(programStats.movelist, p);
7101                 }
7102
7103                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7104                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7105                     DisplayMove(currentMove - 1);
7106                 }
7107                 return;
7108             }
7109         }
7110         else {
7111             buf1[0] = NULLCHAR;
7112
7113             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7114                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7115             {
7116                 ChessProgramStats cpstats;
7117
7118                 if (plyext != ' ' && plyext != '\t') {
7119                     time *= 100;
7120                 }
7121
7122                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7123                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7124                     curscore = -curscore;
7125                 }
7126
7127                 cpstats.depth = plylev;
7128                 cpstats.nodes = nodes;
7129                 cpstats.time = time;
7130                 cpstats.score = curscore;
7131                 cpstats.got_only_move = 0;
7132                 cpstats.movelist[0] = '\0';
7133
7134                 if (buf1[0] != NULLCHAR) {
7135                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7136                 }
7137
7138                 cpstats.ok_to_send = 0;
7139                 cpstats.line_is_book = 0;
7140                 cpstats.nr_moves = 0;
7141                 cpstats.moves_left = 0;
7142
7143                 SendProgramStatsToFrontend( cps, &cpstats );
7144             }
7145         }
7146     }
7147 }
7148
7149
7150 /* Parse a game score from the character string "game", and
7151    record it as the history of the current game.  The game
7152    score is NOT assumed to start from the standard position. 
7153    The display is not updated in any way.
7154    */
7155 void
7156 ParseGameHistory(game)
7157      char *game;
7158 {
7159     ChessMove moveType;
7160     int fromX, fromY, toX, toY, boardIndex;
7161     char promoChar;
7162     char *p, *q;
7163     char buf[MSG_SIZ];
7164
7165     if (appData.debugMode)
7166       fprintf(debugFP, "Parsing game history: %s\n", game);
7167
7168     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7169     gameInfo.site = StrSave(appData.icsHost);
7170     gameInfo.date = PGNDate();
7171     gameInfo.round = StrSave("-");
7172
7173     /* Parse out names of players */
7174     while (*game == ' ') game++;
7175     p = buf;
7176     while (*game != ' ') *p++ = *game++;
7177     *p = NULLCHAR;
7178     gameInfo.white = StrSave(buf);
7179     while (*game == ' ') game++;
7180     p = buf;
7181     while (*game != ' ' && *game != '\n') *p++ = *game++;
7182     *p = NULLCHAR;
7183     gameInfo.black = StrSave(buf);
7184
7185     /* Parse moves */
7186     boardIndex = blackPlaysFirst ? 1 : 0;
7187     yynewstr(game);
7188     for (;;) {
7189         yyboardindex = boardIndex;
7190         moveType = (ChessMove) yylex();
7191         switch (moveType) {
7192           case IllegalMove:             /* maybe suicide chess, etc. */
7193   if (appData.debugMode) {
7194     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7195     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7196     setbuf(debugFP, NULL);
7197   }
7198           case WhitePromotionChancellor:
7199           case BlackPromotionChancellor:
7200           case WhitePromotionArchbishop:
7201           case BlackPromotionArchbishop:
7202           case WhitePromotionQueen:
7203           case BlackPromotionQueen:
7204           case WhitePromotionRook:
7205           case BlackPromotionRook:
7206           case WhitePromotionBishop:
7207           case BlackPromotionBishop:
7208           case WhitePromotionKnight:
7209           case BlackPromotionKnight:
7210           case WhitePromotionKing:
7211           case BlackPromotionKing:
7212           case NormalMove:
7213           case WhiteCapturesEnPassant:
7214           case BlackCapturesEnPassant:
7215           case WhiteKingSideCastle:
7216           case WhiteQueenSideCastle:
7217           case BlackKingSideCastle:
7218           case BlackQueenSideCastle:
7219           case WhiteKingSideCastleWild:
7220           case WhiteQueenSideCastleWild:
7221           case BlackKingSideCastleWild:
7222           case BlackQueenSideCastleWild:
7223           /* PUSH Fabien */
7224           case WhiteHSideCastleFR:
7225           case WhiteASideCastleFR:
7226           case BlackHSideCastleFR:
7227           case BlackASideCastleFR:
7228           /* POP Fabien */
7229             fromX = currentMoveString[0] - AAA;
7230             fromY = currentMoveString[1] - ONE;
7231             toX = currentMoveString[2] - AAA;
7232             toY = currentMoveString[3] - ONE;
7233             promoChar = currentMoveString[4];
7234             break;
7235           case WhiteDrop:
7236           case BlackDrop:
7237             fromX = moveType == WhiteDrop ?
7238               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7239             (int) CharToPiece(ToLower(currentMoveString[0]));
7240             fromY = DROP_RANK;
7241             toX = currentMoveString[2] - AAA;
7242             toY = currentMoveString[3] - ONE;
7243             promoChar = NULLCHAR;
7244             break;
7245           case AmbiguousMove:
7246             /* bug? */
7247             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7248   if (appData.debugMode) {
7249     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7250     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7251     setbuf(debugFP, NULL);
7252   }
7253             DisplayError(buf, 0);
7254             return;
7255           case ImpossibleMove:
7256             /* bug? */
7257             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7258   if (appData.debugMode) {
7259     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7260     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7261     setbuf(debugFP, NULL);
7262   }
7263             DisplayError(buf, 0);
7264             return;
7265           case (ChessMove) 0:   /* end of file */
7266             if (boardIndex < backwardMostMove) {
7267                 /* Oops, gap.  How did that happen? */
7268                 DisplayError(_("Gap in move list"), 0);
7269                 return;
7270             }
7271             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7272             if (boardIndex > forwardMostMove) {
7273                 forwardMostMove = boardIndex;
7274             }
7275             return;
7276           case ElapsedTime:
7277             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7278                 strcat(parseList[boardIndex-1], " ");
7279                 strcat(parseList[boardIndex-1], yy_text);
7280             }
7281             continue;
7282           case Comment:
7283           case PGNTag:
7284           case NAG:
7285           default:
7286             /* ignore */
7287             continue;
7288           case WhiteWins:
7289           case BlackWins:
7290           case GameIsDrawn:
7291           case GameUnfinished:
7292             if (gameMode == IcsExamining) {
7293                 if (boardIndex < backwardMostMove) {
7294                     /* Oops, gap.  How did that happen? */
7295                     return;
7296                 }
7297                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7298                 return;
7299             }
7300             gameInfo.result = moveType;
7301             p = strchr(yy_text, '{');
7302             if (p == NULL) p = strchr(yy_text, '(');
7303             if (p == NULL) {
7304                 p = yy_text;
7305                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7306             } else {
7307                 q = strchr(p, *p == '{' ? '}' : ')');
7308                 if (q != NULL) *q = NULLCHAR;
7309                 p++;
7310             }
7311             gameInfo.resultDetails = StrSave(p);
7312             continue;
7313         }
7314         if (boardIndex >= forwardMostMove &&
7315             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7316             backwardMostMove = blackPlaysFirst ? 1 : 0;
7317             return;
7318         }
7319         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7320                                  fromY, fromX, toY, toX, promoChar,
7321                                  parseList[boardIndex]);
7322         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7323         /* currentMoveString is set as a side-effect of yylex */
7324         strcpy(moveList[boardIndex], currentMoveString);
7325         strcat(moveList[boardIndex], "\n");
7326         boardIndex++;
7327         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7328         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7329           case MT_NONE:
7330           case MT_STALEMATE:
7331           default:
7332             break;
7333           case MT_CHECK:
7334             if(gameInfo.variant != VariantShogi)
7335                 strcat(parseList[boardIndex - 1], "+");
7336             break;
7337           case MT_CHECKMATE:
7338           case MT_STAINMATE:
7339             strcat(parseList[boardIndex - 1], "#");
7340             break;
7341         }
7342     }
7343 }
7344
7345
7346 /* Apply a move to the given board  */
7347 void
7348 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7349      int fromX, fromY, toX, toY;
7350      int promoChar;
7351      Board board;
7352 {
7353   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7354
7355     /* [HGM] compute & store e.p. status and castling rights for new position */
7356     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7357     { int i;
7358
7359       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7360       oldEP = (signed char)board[EP_STATUS];
7361       board[EP_STATUS] = EP_NONE;
7362
7363       if( board[toY][toX] != EmptySquare ) 
7364            board[EP_STATUS] = EP_CAPTURE;  
7365
7366       if( board[fromY][fromX] == WhitePawn ) {
7367            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7368                board[EP_STATUS] = EP_PAWN_MOVE;
7369            if( toY-fromY==2) {
7370                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7371                         gameInfo.variant != VariantBerolina || toX < fromX)
7372                       board[EP_STATUS] = toX | berolina;
7373                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7374                         gameInfo.variant != VariantBerolina || toX > fromX) 
7375                       board[EP_STATUS] = toX;
7376            }
7377       } else 
7378       if( board[fromY][fromX] == BlackPawn ) {
7379            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7380                board[EP_STATUS] = EP_PAWN_MOVE; 
7381            if( toY-fromY== -2) {
7382                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7383                         gameInfo.variant != VariantBerolina || toX < fromX)
7384                       board[EP_STATUS] = toX | berolina;
7385                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7386                         gameInfo.variant != VariantBerolina || toX > fromX) 
7387                       board[EP_STATUS] = toX;
7388            }
7389        }
7390
7391        for(i=0; i<nrCastlingRights; i++) {
7392            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7393               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7394              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7395        }
7396
7397     }
7398
7399   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7400   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7401        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7402          
7403   if (fromX == toX && fromY == toY) return;
7404
7405   if (fromY == DROP_RANK) {
7406         /* must be first */
7407         piece = board[toY][toX] = (ChessSquare) fromX;
7408   } else {
7409      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7410      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7411      if(gameInfo.variant == VariantKnightmate)
7412          king += (int) WhiteUnicorn - (int) WhiteKing;
7413
7414     /* Code added by Tord: */
7415     /* FRC castling assumed when king captures friendly rook. */
7416     if (board[fromY][fromX] == WhiteKing &&
7417              board[toY][toX] == WhiteRook) {
7418       board[fromY][fromX] = EmptySquare;
7419       board[toY][toX] = EmptySquare;
7420       if(toX > fromX) {
7421         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7422       } else {
7423         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7424       }
7425     } else if (board[fromY][fromX] == BlackKing &&
7426                board[toY][toX] == BlackRook) {
7427       board[fromY][fromX] = EmptySquare;
7428       board[toY][toX] = EmptySquare;
7429       if(toX > fromX) {
7430         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7431       } else {
7432         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7433       }
7434     /* End of code added by Tord */
7435
7436     } else if (board[fromY][fromX] == king
7437         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7438         && toY == fromY && toX > fromX+1) {
7439         board[fromY][fromX] = EmptySquare;
7440         board[toY][toX] = king;
7441         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7442         board[fromY][BOARD_RGHT-1] = EmptySquare;
7443     } else if (board[fromY][fromX] == king
7444         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7445                && toY == fromY && toX < fromX-1) {
7446         board[fromY][fromX] = EmptySquare;
7447         board[toY][toX] = king;
7448         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7449         board[fromY][BOARD_LEFT] = EmptySquare;
7450     } else if (board[fromY][fromX] == WhitePawn
7451                && toY == BOARD_HEIGHT-1
7452                && gameInfo.variant != VariantXiangqi
7453                ) {
7454         /* white pawn promotion */
7455         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7456         if (board[toY][toX] == EmptySquare) {
7457             board[toY][toX] = WhiteQueen;
7458         }
7459         if(gameInfo.variant==VariantBughouse ||
7460            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7461             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7462         board[fromY][fromX] = EmptySquare;
7463     } else if ((fromY == BOARD_HEIGHT-4)
7464                && (toX != fromX)
7465                && gameInfo.variant != VariantXiangqi
7466                && gameInfo.variant != VariantBerolina
7467                && (board[fromY][fromX] == WhitePawn)
7468                && (board[toY][toX] == EmptySquare)) {
7469         board[fromY][fromX] = EmptySquare;
7470         board[toY][toX] = WhitePawn;
7471         captured = board[toY - 1][toX];
7472         board[toY - 1][toX] = EmptySquare;
7473     } else if ((fromY == BOARD_HEIGHT-4)
7474                && (toX == fromX)
7475                && gameInfo.variant == VariantBerolina
7476                && (board[fromY][fromX] == WhitePawn)
7477                && (board[toY][toX] == EmptySquare)) {
7478         board[fromY][fromX] = EmptySquare;
7479         board[toY][toX] = WhitePawn;
7480         if(oldEP & EP_BEROLIN_A) {
7481                 captured = board[fromY][fromX-1];
7482                 board[fromY][fromX-1] = EmptySquare;
7483         }else{  captured = board[fromY][fromX+1];
7484                 board[fromY][fromX+1] = EmptySquare;
7485         }
7486     } else if (board[fromY][fromX] == king
7487         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7488                && toY == fromY && toX > fromX+1) {
7489         board[fromY][fromX] = EmptySquare;
7490         board[toY][toX] = king;
7491         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7492         board[fromY][BOARD_RGHT-1] = EmptySquare;
7493     } else if (board[fromY][fromX] == king
7494         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7495                && toY == fromY && toX < fromX-1) {
7496         board[fromY][fromX] = EmptySquare;
7497         board[toY][toX] = king;
7498         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7499         board[fromY][BOARD_LEFT] = EmptySquare;
7500     } else if (fromY == 7 && fromX == 3
7501                && board[fromY][fromX] == BlackKing
7502                && toY == 7 && toX == 5) {
7503         board[fromY][fromX] = EmptySquare;
7504         board[toY][toX] = BlackKing;
7505         board[fromY][7] = EmptySquare;
7506         board[toY][4] = BlackRook;
7507     } else if (fromY == 7 && fromX == 3
7508                && board[fromY][fromX] == BlackKing
7509                && toY == 7 && toX == 1) {
7510         board[fromY][fromX] = EmptySquare;
7511         board[toY][toX] = BlackKing;
7512         board[fromY][0] = EmptySquare;
7513         board[toY][2] = BlackRook;
7514     } else if (board[fromY][fromX] == BlackPawn
7515                && toY == 0
7516                && gameInfo.variant != VariantXiangqi
7517                ) {
7518         /* black pawn promotion */
7519         board[0][toX] = CharToPiece(ToLower(promoChar));
7520         if (board[0][toX] == EmptySquare) {
7521             board[0][toX] = BlackQueen;
7522         }
7523         if(gameInfo.variant==VariantBughouse ||
7524            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7525             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7526         board[fromY][fromX] = EmptySquare;
7527     } else if ((fromY == 3)
7528                && (toX != fromX)
7529                && gameInfo.variant != VariantXiangqi
7530                && gameInfo.variant != VariantBerolina
7531                && (board[fromY][fromX] == BlackPawn)
7532                && (board[toY][toX] == EmptySquare)) {
7533         board[fromY][fromX] = EmptySquare;
7534         board[toY][toX] = BlackPawn;
7535         captured = board[toY + 1][toX];
7536         board[toY + 1][toX] = EmptySquare;
7537     } else if ((fromY == 3)
7538                && (toX == fromX)
7539                && gameInfo.variant == VariantBerolina
7540                && (board[fromY][fromX] == BlackPawn)
7541                && (board[toY][toX] == EmptySquare)) {
7542         board[fromY][fromX] = EmptySquare;
7543         board[toY][toX] = BlackPawn;
7544         if(oldEP & EP_BEROLIN_A) {
7545                 captured = board[fromY][fromX-1];
7546                 board[fromY][fromX-1] = EmptySquare;
7547         }else{  captured = board[fromY][fromX+1];
7548                 board[fromY][fromX+1] = EmptySquare;
7549         }
7550     } else {
7551         board[toY][toX] = board[fromY][fromX];
7552         board[fromY][fromX] = EmptySquare;
7553     }
7554
7555     /* [HGM] now we promote for Shogi, if needed */
7556     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7557         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7558   }
7559
7560     if (gameInfo.holdingsWidth != 0) {
7561
7562       /* !!A lot more code needs to be written to support holdings  */
7563       /* [HGM] OK, so I have written it. Holdings are stored in the */
7564       /* penultimate board files, so they are automaticlly stored   */
7565       /* in the game history.                                       */
7566       if (fromY == DROP_RANK) {
7567         /* Delete from holdings, by decreasing count */
7568         /* and erasing image if necessary            */
7569         p = (int) fromX;
7570         if(p < (int) BlackPawn) { /* white drop */
7571              p -= (int)WhitePawn;
7572                  p = PieceToNumber((ChessSquare)p);
7573              if(p >= gameInfo.holdingsSize) p = 0;
7574              if(--board[p][BOARD_WIDTH-2] <= 0)
7575                   board[p][BOARD_WIDTH-1] = EmptySquare;
7576              if((int)board[p][BOARD_WIDTH-2] < 0)
7577                         board[p][BOARD_WIDTH-2] = 0;
7578         } else {                  /* black drop */
7579              p -= (int)BlackPawn;
7580                  p = PieceToNumber((ChessSquare)p);
7581              if(p >= gameInfo.holdingsSize) p = 0;
7582              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7583                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7584              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7585                         board[BOARD_HEIGHT-1-p][1] = 0;
7586         }
7587       }
7588       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7589           && gameInfo.variant != VariantBughouse        ) {
7590         /* [HGM] holdings: Add to holdings, if holdings exist */
7591         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7592                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7593                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7594         }
7595         p = (int) captured;
7596         if (p >= (int) BlackPawn) {
7597           p -= (int)BlackPawn;
7598           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7599                   /* in Shogi restore piece to its original  first */
7600                   captured = (ChessSquare) (DEMOTED captured);
7601                   p = DEMOTED p;
7602           }
7603           p = PieceToNumber((ChessSquare)p);
7604           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7605           board[p][BOARD_WIDTH-2]++;
7606           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7607         } else {
7608           p -= (int)WhitePawn;
7609           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7610                   captured = (ChessSquare) (DEMOTED captured);
7611                   p = DEMOTED p;
7612           }
7613           p = PieceToNumber((ChessSquare)p);
7614           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7615           board[BOARD_HEIGHT-1-p][1]++;
7616           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7617         }
7618       }
7619     } else if (gameInfo.variant == VariantAtomic) {
7620       if (captured != EmptySquare) {
7621         int y, x;
7622         for (y = toY-1; y <= toY+1; y++) {
7623           for (x = toX-1; x <= toX+1; x++) {
7624             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7625                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7626               board[y][x] = EmptySquare;
7627             }
7628           }
7629         }
7630         board[toY][toX] = EmptySquare;
7631       }
7632     }
7633     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7634         /* [HGM] Shogi promotions */
7635         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7636     }
7637
7638     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7639                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7640         // [HGM] superchess: take promotion piece out of holdings
7641         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7642         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7643             if(!--board[k][BOARD_WIDTH-2])
7644                 board[k][BOARD_WIDTH-1] = EmptySquare;
7645         } else {
7646             if(!--board[BOARD_HEIGHT-1-k][1])
7647                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7648         }
7649     }
7650
7651 }
7652
7653 /* Updates forwardMostMove */
7654 void
7655 MakeMove(fromX, fromY, toX, toY, promoChar)
7656      int fromX, fromY, toX, toY;
7657      int promoChar;
7658 {
7659 //    forwardMostMove++; // [HGM] bare: moved downstream
7660
7661     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7662         int timeLeft; static int lastLoadFlag=0; int king, piece;
7663         piece = boards[forwardMostMove][fromY][fromX];
7664         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7665         if(gameInfo.variant == VariantKnightmate)
7666             king += (int) WhiteUnicorn - (int) WhiteKing;
7667         if(forwardMostMove == 0) {
7668             if(blackPlaysFirst) 
7669                 fprintf(serverMoves, "%s;", second.tidy);
7670             fprintf(serverMoves, "%s;", first.tidy);
7671             if(!blackPlaysFirst) 
7672                 fprintf(serverMoves, "%s;", second.tidy);
7673         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7674         lastLoadFlag = loadFlag;
7675         // print base move
7676         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7677         // print castling suffix
7678         if( toY == fromY && piece == king ) {
7679             if(toX-fromX > 1)
7680                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7681             if(fromX-toX >1)
7682                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7683         }
7684         // e.p. suffix
7685         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7686              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7687              boards[forwardMostMove][toY][toX] == EmptySquare
7688              && fromX != toX )
7689                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7690         // promotion suffix
7691         if(promoChar != NULLCHAR)
7692                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7693         if(!loadFlag) {
7694             fprintf(serverMoves, "/%d/%d",
7695                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7696             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7697             else                      timeLeft = blackTimeRemaining/1000;
7698             fprintf(serverMoves, "/%d", timeLeft);
7699         }
7700         fflush(serverMoves);
7701     }
7702
7703     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7704       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7705                         0, 1);
7706       return;
7707     }
7708     if (commentList[forwardMostMove+1] != NULL) {
7709         free(commentList[forwardMostMove+1]);
7710         commentList[forwardMostMove+1] = NULL;
7711     }
7712     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7713     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7714     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7715     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7716     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7717     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7718     gameInfo.result = GameUnfinished;
7719     if (gameInfo.resultDetails != NULL) {
7720         free(gameInfo.resultDetails);
7721         gameInfo.resultDetails = NULL;
7722     }
7723     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7724                               moveList[forwardMostMove - 1]);
7725     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7726                              PosFlags(forwardMostMove - 1),
7727                              fromY, fromX, toY, toX, promoChar,
7728                              parseList[forwardMostMove - 1]);
7729     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7730       case MT_NONE:
7731       case MT_STALEMATE:
7732       default:
7733         break;
7734       case MT_CHECK:
7735         if(gameInfo.variant != VariantShogi)
7736             strcat(parseList[forwardMostMove - 1], "+");
7737         break;
7738       case MT_CHECKMATE:
7739       case MT_STAINMATE:
7740         strcat(parseList[forwardMostMove - 1], "#");
7741         break;
7742     }
7743     if (appData.debugMode) {
7744         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7745     }
7746
7747 }
7748
7749 /* Updates currentMove if not pausing */
7750 void
7751 ShowMove(fromX, fromY, toX, toY)
7752 {
7753     int instant = (gameMode == PlayFromGameFile) ?
7754         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7755     if(appData.noGUI) return;
7756     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7757         if (!instant) {
7758             if (forwardMostMove == currentMove + 1) {
7759                 AnimateMove(boards[forwardMostMove - 1],
7760                             fromX, fromY, toX, toY);
7761             }
7762             if (appData.highlightLastMove) {
7763                 SetHighlights(fromX, fromY, toX, toY);
7764             }
7765         }
7766         currentMove = forwardMostMove;
7767     }
7768
7769     if (instant) return;
7770
7771     DisplayMove(currentMove - 1);
7772     DrawPosition(FALSE, boards[currentMove]);
7773     DisplayBothClocks();
7774     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7775 }
7776
7777 void SendEgtPath(ChessProgramState *cps)
7778 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7779         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7780
7781         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7782
7783         while(*p) {
7784             char c, *q = name+1, *r, *s;
7785
7786             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7787             while(*p && *p != ',') *q++ = *p++;
7788             *q++ = ':'; *q = 0;
7789             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7790                 strcmp(name, ",nalimov:") == 0 ) {
7791                 // take nalimov path from the menu-changeable option first, if it is defined
7792                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7793                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7794             } else
7795             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7796                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7797                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7798                 s = r = StrStr(s, ":") + 1; // beginning of path info
7799                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7800                 c = *r; *r = 0;             // temporarily null-terminate path info
7801                     *--q = 0;               // strip of trailig ':' from name
7802                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7803                 *r = c;
7804                 SendToProgram(buf,cps);     // send egtbpath command for this format
7805             }
7806             if(*p == ',') p++; // read away comma to position for next format name
7807         }
7808 }
7809
7810 void
7811 InitChessProgram(cps, setup)
7812      ChessProgramState *cps;
7813      int setup; /* [HGM] needed to setup FRC opening position */
7814 {
7815     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7816     if (appData.noChessProgram) return;
7817     hintRequested = FALSE;
7818     bookRequested = FALSE;
7819
7820     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7821     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7822     if(cps->memSize) { /* [HGM] memory */
7823         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7824         SendToProgram(buf, cps);
7825     }
7826     SendEgtPath(cps); /* [HGM] EGT */
7827     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7828         sprintf(buf, "cores %d\n", appData.smpCores);
7829         SendToProgram(buf, cps);
7830     }
7831
7832     SendToProgram(cps->initString, cps);
7833     if (gameInfo.variant != VariantNormal &&
7834         gameInfo.variant != VariantLoadable
7835         /* [HGM] also send variant if board size non-standard */
7836         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7837                                             ) {
7838       char *v = VariantName(gameInfo.variant);
7839       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7840         /* [HGM] in protocol 1 we have to assume all variants valid */
7841         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7842         DisplayFatalError(buf, 0, 1);
7843         return;
7844       }
7845
7846       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7847       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7848       if( gameInfo.variant == VariantXiangqi )
7849            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7850       if( gameInfo.variant == VariantShogi )
7851            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7852       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7853            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7854       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7855                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7856            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7857       if( gameInfo.variant == VariantCourier )
7858            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7859       if( gameInfo.variant == VariantSuper )
7860            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7861       if( gameInfo.variant == VariantGreat )
7862            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7863
7864       if(overruled) {
7865            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7866                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7867            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7868            if(StrStr(cps->variants, b) == NULL) { 
7869                // specific sized variant not known, check if general sizing allowed
7870                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7871                    if(StrStr(cps->variants, "boardsize") == NULL) {
7872                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7873                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7874                        DisplayFatalError(buf, 0, 1);
7875                        return;
7876                    }
7877                    /* [HGM] here we really should compare with the maximum supported board size */
7878                }
7879            }
7880       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7881       sprintf(buf, "variant %s\n", b);
7882       SendToProgram(buf, cps);
7883     }
7884     currentlyInitializedVariant = gameInfo.variant;
7885
7886     /* [HGM] send opening position in FRC to first engine */
7887     if(setup) {
7888           SendToProgram("force\n", cps);
7889           SendBoard(cps, 0);
7890           /* engine is now in force mode! Set flag to wake it up after first move. */
7891           setboardSpoiledMachineBlack = 1;
7892     }
7893
7894     if (cps->sendICS) {
7895       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7896       SendToProgram(buf, cps);
7897     }
7898     cps->maybeThinking = FALSE;
7899     cps->offeredDraw = 0;
7900     if (!appData.icsActive) {
7901         SendTimeControl(cps, movesPerSession, timeControl,
7902                         timeIncrement, appData.searchDepth,
7903                         searchTime);
7904     }
7905     if (appData.showThinking 
7906         // [HGM] thinking: four options require thinking output to be sent
7907         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7908                                 ) {
7909         SendToProgram("post\n", cps);
7910     }
7911     SendToProgram("hard\n", cps);
7912     if (!appData.ponderNextMove) {
7913         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7914            it without being sure what state we are in first.  "hard"
7915            is not a toggle, so that one is OK.
7916          */
7917         SendToProgram("easy\n", cps);
7918     }
7919     if (cps->usePing) {
7920       sprintf(buf, "ping %d\n", ++cps->lastPing);
7921       SendToProgram(buf, cps);
7922     }
7923     cps->initDone = TRUE;
7924 }   
7925
7926
7927 void
7928 StartChessProgram(cps)
7929      ChessProgramState *cps;
7930 {
7931     char buf[MSG_SIZ];
7932     int err;
7933
7934     if (appData.noChessProgram) return;
7935     cps->initDone = FALSE;
7936
7937     if (strcmp(cps->host, "localhost") == 0) {
7938         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7939     } else if (*appData.remoteShell == NULLCHAR) {
7940         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7941     } else {
7942         if (*appData.remoteUser == NULLCHAR) {
7943           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7944                     cps->program);
7945         } else {
7946           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7947                     cps->host, appData.remoteUser, cps->program);
7948         }
7949         err = StartChildProcess(buf, "", &cps->pr);
7950     }
7951     
7952     if (err != 0) {
7953         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7954         DisplayFatalError(buf, err, 1);
7955         cps->pr = NoProc;
7956         cps->isr = NULL;
7957         return;
7958     }
7959     
7960     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7961     if (cps->protocolVersion > 1) {
7962       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7963       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7964       cps->comboCnt = 0;  //                and values of combo boxes
7965       SendToProgram(buf, cps);
7966     } else {
7967       SendToProgram("xboard\n", cps);
7968     }
7969 }
7970
7971
7972 void
7973 TwoMachinesEventIfReady P((void))
7974 {
7975   if (first.lastPing != first.lastPong) {
7976     DisplayMessage("", _("Waiting for first chess program"));
7977     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7978     return;
7979   }
7980   if (second.lastPing != second.lastPong) {
7981     DisplayMessage("", _("Waiting for second chess program"));
7982     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7983     return;
7984   }
7985   ThawUI();
7986   TwoMachinesEvent();
7987 }
7988
7989 void
7990 NextMatchGame P((void))
7991 {
7992     int index; /* [HGM] autoinc: step load index during match */
7993     Reset(FALSE, TRUE);
7994     if (*appData.loadGameFile != NULLCHAR) {
7995         index = appData.loadGameIndex;
7996         if(index < 0) { // [HGM] autoinc
7997             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7998             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7999         } 
8000         LoadGameFromFile(appData.loadGameFile,
8001                          index,
8002                          appData.loadGameFile, FALSE);
8003     } else if (*appData.loadPositionFile != NULLCHAR) {
8004         index = appData.loadPositionIndex;
8005         if(index < 0) { // [HGM] autoinc
8006             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8007             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8008         } 
8009         LoadPositionFromFile(appData.loadPositionFile,
8010                              index,
8011                              appData.loadPositionFile);
8012     }
8013     TwoMachinesEventIfReady();
8014 }
8015
8016 void UserAdjudicationEvent( int result )
8017 {
8018     ChessMove gameResult = GameIsDrawn;
8019
8020     if( result > 0 ) {
8021         gameResult = WhiteWins;
8022     }
8023     else if( result < 0 ) {
8024         gameResult = BlackWins;
8025     }
8026
8027     if( gameMode == TwoMachinesPlay ) {
8028         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8029     }
8030 }
8031
8032
8033 // [HGM] save: calculate checksum of game to make games easily identifiable
8034 int StringCheckSum(char *s)
8035 {
8036         int i = 0;
8037         if(s==NULL) return 0;
8038         while(*s) i = i*259 + *s++;
8039         return i;
8040 }
8041
8042 int GameCheckSum()
8043 {
8044         int i, sum=0;
8045         for(i=backwardMostMove; i<forwardMostMove; i++) {
8046                 sum += pvInfoList[i].depth;
8047                 sum += StringCheckSum(parseList[i]);
8048                 sum += StringCheckSum(commentList[i]);
8049                 sum *= 261;
8050         }
8051         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8052         return sum + StringCheckSum(commentList[i]);
8053 } // end of save patch
8054
8055 void
8056 GameEnds(result, resultDetails, whosays)
8057      ChessMove result;
8058      char *resultDetails;
8059      int whosays;
8060 {
8061     GameMode nextGameMode;
8062     int isIcsGame;
8063     char buf[MSG_SIZ];
8064
8065     if(endingGame) return; /* [HGM] crash: forbid recursion */
8066     endingGame = 1;
8067
8068     if (appData.debugMode) {
8069       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8070               result, resultDetails ? resultDetails : "(null)", whosays);
8071     }
8072
8073     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8074         /* If we are playing on ICS, the server decides when the
8075            game is over, but the engine can offer to draw, claim 
8076            a draw, or resign. 
8077          */
8078 #if ZIPPY
8079         if (appData.zippyPlay && first.initDone) {
8080             if (result == GameIsDrawn) {
8081                 /* In case draw still needs to be claimed */
8082                 SendToICS(ics_prefix);
8083                 SendToICS("draw\n");
8084             } else if (StrCaseStr(resultDetails, "resign")) {
8085                 SendToICS(ics_prefix);
8086                 SendToICS("resign\n");
8087             }
8088         }
8089 #endif
8090         endingGame = 0; /* [HGM] crash */
8091         return;
8092     }
8093
8094     /* If we're loading the game from a file, stop */
8095     if (whosays == GE_FILE) {
8096       (void) StopLoadGameTimer();
8097       gameFileFP = NULL;
8098     }
8099
8100     /* Cancel draw offers */
8101     first.offeredDraw = second.offeredDraw = 0;
8102
8103     /* If this is an ICS game, only ICS can really say it's done;
8104        if not, anyone can. */
8105     isIcsGame = (gameMode == IcsPlayingWhite || 
8106                  gameMode == IcsPlayingBlack || 
8107                  gameMode == IcsObserving    || 
8108                  gameMode == IcsExamining);
8109
8110     if (!isIcsGame || whosays == GE_ICS) {
8111         /* OK -- not an ICS game, or ICS said it was done */
8112         StopClocks();
8113         if (!isIcsGame && !appData.noChessProgram) 
8114           SetUserThinkingEnables();
8115     
8116         /* [HGM] if a machine claims the game end we verify this claim */
8117         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8118             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8119                 char claimer;
8120                 ChessMove trueResult = (ChessMove) -1;
8121
8122                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8123                                             first.twoMachinesColor[0] :
8124                                             second.twoMachinesColor[0] ;
8125
8126                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8127                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8128                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8129                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8130                 } else
8131                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8132                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8133                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8134                 } else
8135                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8136                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8137                 }
8138
8139                 // now verify win claims, but not in drop games, as we don't understand those yet
8140                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8141                                                  || gameInfo.variant == VariantGreat) &&
8142                     (result == WhiteWins && claimer == 'w' ||
8143                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8144                       if (appData.debugMode) {
8145                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8146                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8147                       }
8148                       if(result != trueResult) {
8149                               sprintf(buf, "False win claim: '%s'", resultDetails);
8150                               result = claimer == 'w' ? BlackWins : WhiteWins;
8151                               resultDetails = buf;
8152                       }
8153                 } else
8154                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8155                     && (forwardMostMove <= backwardMostMove ||
8156                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8157                         (claimer=='b')==(forwardMostMove&1))
8158                                                                                   ) {
8159                       /* [HGM] verify: draws that were not flagged are false claims */
8160                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8161                       result = claimer == 'w' ? BlackWins : WhiteWins;
8162                       resultDetails = buf;
8163                 }
8164                 /* (Claiming a loss is accepted no questions asked!) */
8165             }
8166             /* [HGM] bare: don't allow bare King to win */
8167             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8168                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8169                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8170                && result != GameIsDrawn)
8171             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8172                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8173                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8174                         if(p >= 0 && p <= (int)WhiteKing) k++;
8175                 }
8176                 if (appData.debugMode) {
8177                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8178                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8179                 }
8180                 if(k <= 1) {
8181                         result = GameIsDrawn;
8182                         sprintf(buf, "%s but bare king", resultDetails);
8183                         resultDetails = buf;
8184                 }
8185             }
8186         }
8187
8188
8189         if(serverMoves != NULL && !loadFlag) { char c = '=';
8190             if(result==WhiteWins) c = '+';
8191             if(result==BlackWins) c = '-';
8192             if(resultDetails != NULL)
8193                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8194         }
8195         if (resultDetails != NULL) {
8196             gameInfo.result = result;
8197             gameInfo.resultDetails = StrSave(resultDetails);
8198
8199             /* display last move only if game was not loaded from file */
8200             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8201                 DisplayMove(currentMove - 1);
8202     
8203             if (forwardMostMove != 0) {
8204                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8205                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8206                                                                 ) {
8207                     if (*appData.saveGameFile != NULLCHAR) {
8208                         SaveGameToFile(appData.saveGameFile, TRUE);
8209                     } else if (appData.autoSaveGames) {
8210                         AutoSaveGame();
8211                     }
8212                     if (*appData.savePositionFile != NULLCHAR) {
8213                         SavePositionToFile(appData.savePositionFile);
8214                     }
8215                 }
8216             }
8217
8218             /* Tell program how game ended in case it is learning */
8219             /* [HGM] Moved this to after saving the PGN, just in case */
8220             /* engine died and we got here through time loss. In that */
8221             /* case we will get a fatal error writing the pipe, which */
8222             /* would otherwise lose us the PGN.                       */
8223             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8224             /* output during GameEnds should never be fatal anymore   */
8225             if (gameMode == MachinePlaysWhite ||
8226                 gameMode == MachinePlaysBlack ||
8227                 gameMode == TwoMachinesPlay ||
8228                 gameMode == IcsPlayingWhite ||
8229                 gameMode == IcsPlayingBlack ||
8230                 gameMode == BeginningOfGame) {
8231                 char buf[MSG_SIZ];
8232                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8233                         resultDetails);
8234                 if (first.pr != NoProc) {
8235                     SendToProgram(buf, &first);
8236                 }
8237                 if (second.pr != NoProc &&
8238                     gameMode == TwoMachinesPlay) {
8239                     SendToProgram(buf, &second);
8240                 }
8241             }
8242         }
8243
8244         if (appData.icsActive) {
8245             if (appData.quietPlay &&
8246                 (gameMode == IcsPlayingWhite ||
8247                  gameMode == IcsPlayingBlack)) {
8248                 SendToICS(ics_prefix);
8249                 SendToICS("set shout 1\n");
8250             }
8251             nextGameMode = IcsIdle;
8252             ics_user_moved = FALSE;
8253             /* clean up premove.  It's ugly when the game has ended and the
8254              * premove highlights are still on the board.
8255              */
8256             if (gotPremove) {
8257               gotPremove = FALSE;
8258               ClearPremoveHighlights();
8259               DrawPosition(FALSE, boards[currentMove]);
8260             }
8261             if (whosays == GE_ICS) {
8262                 switch (result) {
8263                 case WhiteWins:
8264                     if (gameMode == IcsPlayingWhite)
8265                         PlayIcsWinSound();
8266                     else if(gameMode == IcsPlayingBlack)
8267                         PlayIcsLossSound();
8268                     break;
8269                 case BlackWins:
8270                     if (gameMode == IcsPlayingBlack)
8271                         PlayIcsWinSound();
8272                     else if(gameMode == IcsPlayingWhite)
8273                         PlayIcsLossSound();
8274                     break;
8275                 case GameIsDrawn:
8276                     PlayIcsDrawSound();
8277                     break;
8278                 default:
8279                     PlayIcsUnfinishedSound();
8280                 }
8281             }
8282         } else if (gameMode == EditGame ||
8283                    gameMode == PlayFromGameFile || 
8284                    gameMode == AnalyzeMode || 
8285                    gameMode == AnalyzeFile) {
8286             nextGameMode = gameMode;
8287         } else {
8288             nextGameMode = EndOfGame;
8289         }
8290         pausing = FALSE;
8291         ModeHighlight();
8292     } else {
8293         nextGameMode = gameMode;
8294     }
8295
8296     if (appData.noChessProgram) {
8297         gameMode = nextGameMode;
8298         ModeHighlight();
8299         endingGame = 0; /* [HGM] crash */
8300         return;
8301     }
8302
8303     if (first.reuse) {
8304         /* Put first chess program into idle state */
8305         if (first.pr != NoProc &&
8306             (gameMode == MachinePlaysWhite ||
8307              gameMode == MachinePlaysBlack ||
8308              gameMode == TwoMachinesPlay ||
8309              gameMode == IcsPlayingWhite ||
8310              gameMode == IcsPlayingBlack ||
8311              gameMode == BeginningOfGame)) {
8312             SendToProgram("force\n", &first);
8313             if (first.usePing) {
8314               char buf[MSG_SIZ];
8315               sprintf(buf, "ping %d\n", ++first.lastPing);
8316               SendToProgram(buf, &first);
8317             }
8318         }
8319     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8320         /* Kill off first chess program */
8321         if (first.isr != NULL)
8322           RemoveInputSource(first.isr);
8323         first.isr = NULL;
8324     
8325         if (first.pr != NoProc) {
8326             ExitAnalyzeMode();
8327             DoSleep( appData.delayBeforeQuit );
8328             SendToProgram("quit\n", &first);
8329             DoSleep( appData.delayAfterQuit );
8330             DestroyChildProcess(first.pr, first.useSigterm);
8331         }
8332         first.pr = NoProc;
8333     }
8334     if (second.reuse) {
8335         /* Put second chess program into idle state */
8336         if (second.pr != NoProc &&
8337             gameMode == TwoMachinesPlay) {
8338             SendToProgram("force\n", &second);
8339             if (second.usePing) {
8340               char buf[MSG_SIZ];
8341               sprintf(buf, "ping %d\n", ++second.lastPing);
8342               SendToProgram(buf, &second);
8343             }
8344         }
8345     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8346         /* Kill off second chess program */
8347         if (second.isr != NULL)
8348           RemoveInputSource(second.isr);
8349         second.isr = NULL;
8350     
8351         if (second.pr != NoProc) {
8352             DoSleep( appData.delayBeforeQuit );
8353             SendToProgram("quit\n", &second);
8354             DoSleep( appData.delayAfterQuit );
8355             DestroyChildProcess(second.pr, second.useSigterm);
8356         }
8357         second.pr = NoProc;
8358     }
8359
8360     if (matchMode && gameMode == TwoMachinesPlay) {
8361         switch (result) {
8362         case WhiteWins:
8363           if (first.twoMachinesColor[0] == 'w') {
8364             first.matchWins++;
8365           } else {
8366             second.matchWins++;
8367           }
8368           break;
8369         case BlackWins:
8370           if (first.twoMachinesColor[0] == 'b') {
8371             first.matchWins++;
8372           } else {
8373             second.matchWins++;
8374           }
8375           break;
8376         default:
8377           break;
8378         }
8379         if (matchGame < appData.matchGames) {
8380             char *tmp;
8381             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8382                 tmp = first.twoMachinesColor;
8383                 first.twoMachinesColor = second.twoMachinesColor;
8384                 second.twoMachinesColor = tmp;
8385             }
8386             gameMode = nextGameMode;
8387             matchGame++;
8388             if(appData.matchPause>10000 || appData.matchPause<10)
8389                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8390             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8391             endingGame = 0; /* [HGM] crash */
8392             return;
8393         } else {
8394             char buf[MSG_SIZ];
8395             gameMode = nextGameMode;
8396             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8397                     first.tidy, second.tidy,
8398                     first.matchWins, second.matchWins,
8399                     appData.matchGames - (first.matchWins + second.matchWins));
8400             DisplayFatalError(buf, 0, 0);
8401         }
8402     }
8403     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8404         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8405       ExitAnalyzeMode();
8406     gameMode = nextGameMode;
8407     ModeHighlight();
8408     endingGame = 0;  /* [HGM] crash */
8409 }
8410
8411 /* Assumes program was just initialized (initString sent).
8412    Leaves program in force mode. */
8413 void
8414 FeedMovesToProgram(cps, upto) 
8415      ChessProgramState *cps;
8416      int upto;
8417 {
8418     int i;
8419     
8420     if (appData.debugMode)
8421       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8422               startedFromSetupPosition ? "position and " : "",
8423               backwardMostMove, upto, cps->which);
8424     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8425         // [HGM] variantswitch: make engine aware of new variant
8426         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8427                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8428         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8429         SendToProgram(buf, cps);
8430         currentlyInitializedVariant = gameInfo.variant;
8431     }
8432     SendToProgram("force\n", cps);
8433     if (startedFromSetupPosition) {
8434         SendBoard(cps, backwardMostMove);
8435     if (appData.debugMode) {
8436         fprintf(debugFP, "feedMoves\n");
8437     }
8438     }
8439     for (i = backwardMostMove; i < upto; i++) {
8440         SendMoveToProgram(i, cps);
8441     }
8442 }
8443
8444
8445 void
8446 ResurrectChessProgram()
8447 {
8448      /* The chess program may have exited.
8449         If so, restart it and feed it all the moves made so far. */
8450
8451     if (appData.noChessProgram || first.pr != NoProc) return;
8452     
8453     StartChessProgram(&first);
8454     InitChessProgram(&first, FALSE);
8455     FeedMovesToProgram(&first, currentMove);
8456
8457     if (!first.sendTime) {
8458         /* can't tell gnuchess what its clock should read,
8459            so we bow to its notion. */
8460         ResetClocks();
8461         timeRemaining[0][currentMove] = whiteTimeRemaining;
8462         timeRemaining[1][currentMove] = blackTimeRemaining;
8463     }
8464
8465     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8466                 appData.icsEngineAnalyze) && first.analysisSupport) {
8467       SendToProgram("analyze\n", &first);
8468       first.analyzing = TRUE;
8469     }
8470 }
8471
8472 /*
8473  * Button procedures
8474  */
8475 void
8476 Reset(redraw, init)
8477      int redraw, init;
8478 {
8479     int i;
8480
8481     if (appData.debugMode) {
8482         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8483                 redraw, init, gameMode);
8484     }
8485     CleanupTail(); // [HGM] vari: delete any stored variations
8486     pausing = pauseExamInvalid = FALSE;
8487     startedFromSetupPosition = blackPlaysFirst = FALSE;
8488     firstMove = TRUE;
8489     whiteFlag = blackFlag = FALSE;
8490     userOfferedDraw = FALSE;
8491     hintRequested = bookRequested = FALSE;
8492     first.maybeThinking = FALSE;
8493     second.maybeThinking = FALSE;
8494     first.bookSuspend = FALSE; // [HGM] book
8495     second.bookSuspend = FALSE;
8496     thinkOutput[0] = NULLCHAR;
8497     lastHint[0] = NULLCHAR;
8498     ClearGameInfo(&gameInfo);
8499     gameInfo.variant = StringToVariant(appData.variant);
8500     ics_user_moved = ics_clock_paused = FALSE;
8501     ics_getting_history = H_FALSE;
8502     ics_gamenum = -1;
8503     white_holding[0] = black_holding[0] = NULLCHAR;
8504     ClearProgramStats();
8505     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8506     
8507     ResetFrontEnd();
8508     ClearHighlights();
8509     flipView = appData.flipView;
8510     ClearPremoveHighlights();
8511     gotPremove = FALSE;
8512     alarmSounded = FALSE;
8513
8514     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8515     if(appData.serverMovesName != NULL) {
8516         /* [HGM] prepare to make moves file for broadcasting */
8517         clock_t t = clock();
8518         if(serverMoves != NULL) fclose(serverMoves);
8519         serverMoves = fopen(appData.serverMovesName, "r");
8520         if(serverMoves != NULL) {
8521             fclose(serverMoves);
8522             /* delay 15 sec before overwriting, so all clients can see end */
8523             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8524         }
8525         serverMoves = fopen(appData.serverMovesName, "w");
8526     }
8527
8528     ExitAnalyzeMode();
8529     gameMode = BeginningOfGame;
8530     ModeHighlight();
8531     if(appData.icsActive) gameInfo.variant = VariantNormal;
8532     currentMove = forwardMostMove = backwardMostMove = 0;
8533     InitPosition(redraw);
8534     for (i = 0; i < MAX_MOVES; i++) {
8535         if (commentList[i] != NULL) {
8536             free(commentList[i]);
8537             commentList[i] = NULL;
8538         }
8539     }
8540     ResetClocks();
8541     timeRemaining[0][0] = whiteTimeRemaining;
8542     timeRemaining[1][0] = blackTimeRemaining;
8543     if (first.pr == NULL) {
8544         StartChessProgram(&first);
8545     }
8546     if (init) {
8547             InitChessProgram(&first, startedFromSetupPosition);
8548     }
8549     DisplayTitle("");
8550     DisplayMessage("", "");
8551     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8552     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8553 }
8554
8555 void
8556 AutoPlayGameLoop()
8557 {
8558     for (;;) {
8559         if (!AutoPlayOneMove())
8560           return;
8561         if (matchMode || appData.timeDelay == 0)
8562           continue;
8563         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8564           return;
8565         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8566         break;
8567     }
8568 }
8569
8570
8571 int
8572 AutoPlayOneMove()
8573 {
8574     int fromX, fromY, toX, toY;
8575
8576     if (appData.debugMode) {
8577       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8578     }
8579
8580     if (gameMode != PlayFromGameFile)
8581       return FALSE;
8582
8583     if (currentMove >= forwardMostMove) {
8584       gameMode = EditGame;
8585       ModeHighlight();
8586
8587       /* [AS] Clear current move marker at the end of a game */
8588       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8589
8590       return FALSE;
8591     }
8592     
8593     toX = moveList[currentMove][2] - AAA;
8594     toY = moveList[currentMove][3] - ONE;
8595
8596     if (moveList[currentMove][1] == '@') {
8597         if (appData.highlightLastMove) {
8598             SetHighlights(-1, -1, toX, toY);
8599         }
8600     } else {
8601         fromX = moveList[currentMove][0] - AAA;
8602         fromY = moveList[currentMove][1] - ONE;
8603
8604         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8605
8606         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8607
8608         if (appData.highlightLastMove) {
8609             SetHighlights(fromX, fromY, toX, toY);
8610         }
8611     }
8612     DisplayMove(currentMove);
8613     SendMoveToProgram(currentMove++, &first);
8614     DisplayBothClocks();
8615     DrawPosition(FALSE, boards[currentMove]);
8616     // [HGM] PV info: always display, routine tests if empty
8617     DisplayComment(currentMove - 1, commentList[currentMove]);
8618     return TRUE;
8619 }
8620
8621
8622 int
8623 LoadGameOneMove(readAhead)
8624      ChessMove readAhead;
8625 {
8626     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8627     char promoChar = NULLCHAR;
8628     ChessMove moveType;
8629     char move[MSG_SIZ];
8630     char *p, *q;
8631     
8632     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8633         gameMode != AnalyzeMode && gameMode != Training) {
8634         gameFileFP = NULL;
8635         return FALSE;
8636     }
8637     
8638     yyboardindex = forwardMostMove;
8639     if (readAhead != (ChessMove)0) {
8640       moveType = readAhead;
8641     } else {
8642       if (gameFileFP == NULL)
8643           return FALSE;
8644       moveType = (ChessMove) yylex();
8645     }
8646     
8647     done = FALSE;
8648     switch (moveType) {
8649       case Comment:
8650         if (appData.debugMode) 
8651           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8652         p = yy_text;
8653
8654         /* append the comment but don't display it */
8655         AppendComment(currentMove, p, FALSE);
8656         return TRUE;
8657
8658       case WhiteCapturesEnPassant:
8659       case BlackCapturesEnPassant:
8660       case WhitePromotionChancellor:
8661       case BlackPromotionChancellor:
8662       case WhitePromotionArchbishop:
8663       case BlackPromotionArchbishop:
8664       case WhitePromotionCentaur:
8665       case BlackPromotionCentaur:
8666       case WhitePromotionQueen:
8667       case BlackPromotionQueen:
8668       case WhitePromotionRook:
8669       case BlackPromotionRook:
8670       case WhitePromotionBishop:
8671       case BlackPromotionBishop:
8672       case WhitePromotionKnight:
8673       case BlackPromotionKnight:
8674       case WhitePromotionKing:
8675       case BlackPromotionKing:
8676       case NormalMove:
8677       case WhiteKingSideCastle:
8678       case WhiteQueenSideCastle:
8679       case BlackKingSideCastle:
8680       case BlackQueenSideCastle:
8681       case WhiteKingSideCastleWild:
8682       case WhiteQueenSideCastleWild:
8683       case BlackKingSideCastleWild:
8684       case BlackQueenSideCastleWild:
8685       /* PUSH Fabien */
8686       case WhiteHSideCastleFR:
8687       case WhiteASideCastleFR:
8688       case BlackHSideCastleFR:
8689       case BlackASideCastleFR:
8690       /* POP Fabien */
8691         if (appData.debugMode)
8692           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8693         fromX = currentMoveString[0] - AAA;
8694         fromY = currentMoveString[1] - ONE;
8695         toX = currentMoveString[2] - AAA;
8696         toY = currentMoveString[3] - ONE;
8697         promoChar = currentMoveString[4];
8698         break;
8699
8700       case WhiteDrop:
8701       case BlackDrop:
8702         if (appData.debugMode)
8703           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8704         fromX = moveType == WhiteDrop ?
8705           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8706         (int) CharToPiece(ToLower(currentMoveString[0]));
8707         fromY = DROP_RANK;
8708         toX = currentMoveString[2] - AAA;
8709         toY = currentMoveString[3] - ONE;
8710         break;
8711
8712       case WhiteWins:
8713       case BlackWins:
8714       case GameIsDrawn:
8715       case GameUnfinished:
8716         if (appData.debugMode)
8717           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8718         p = strchr(yy_text, '{');
8719         if (p == NULL) p = strchr(yy_text, '(');
8720         if (p == NULL) {
8721             p = yy_text;
8722             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8723         } else {
8724             q = strchr(p, *p == '{' ? '}' : ')');
8725             if (q != NULL) *q = NULLCHAR;
8726             p++;
8727         }
8728         GameEnds(moveType, p, GE_FILE);
8729         done = TRUE;
8730         if (cmailMsgLoaded) {
8731             ClearHighlights();
8732             flipView = WhiteOnMove(currentMove);
8733             if (moveType == GameUnfinished) flipView = !flipView;
8734             if (appData.debugMode)
8735               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8736         }
8737         break;
8738
8739       case (ChessMove) 0:       /* end of file */
8740         if (appData.debugMode)
8741           fprintf(debugFP, "Parser hit end of file\n");
8742         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8743           case MT_NONE:
8744           case MT_CHECK:
8745             break;
8746           case MT_CHECKMATE:
8747           case MT_STAINMATE:
8748             if (WhiteOnMove(currentMove)) {
8749                 GameEnds(BlackWins, "Black mates", GE_FILE);
8750             } else {
8751                 GameEnds(WhiteWins, "White mates", GE_FILE);
8752             }
8753             break;
8754           case MT_STALEMATE:
8755             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8756             break;
8757         }
8758         done = TRUE;
8759         break;
8760
8761       case MoveNumberOne:
8762         if (lastLoadGameStart == GNUChessGame) {
8763             /* GNUChessGames have numbers, but they aren't move numbers */
8764             if (appData.debugMode)
8765               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8766                       yy_text, (int) moveType);
8767             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8768         }
8769         /* else fall thru */
8770
8771       case XBoardGame:
8772       case GNUChessGame:
8773       case PGNTag:
8774         /* Reached start of next game in file */
8775         if (appData.debugMode)
8776           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8777         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8778           case MT_NONE:
8779           case MT_CHECK:
8780             break;
8781           case MT_CHECKMATE:
8782           case MT_STAINMATE:
8783             if (WhiteOnMove(currentMove)) {
8784                 GameEnds(BlackWins, "Black mates", GE_FILE);
8785             } else {
8786                 GameEnds(WhiteWins, "White mates", GE_FILE);
8787             }
8788             break;
8789           case MT_STALEMATE:
8790             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8791             break;
8792         }
8793         done = TRUE;
8794         break;
8795
8796       case PositionDiagram:     /* should not happen; ignore */
8797       case ElapsedTime:         /* ignore */
8798       case NAG:                 /* ignore */
8799         if (appData.debugMode)
8800           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8801                   yy_text, (int) moveType);
8802         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8803
8804       case IllegalMove:
8805         if (appData.testLegality) {
8806             if (appData.debugMode)
8807               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8808             sprintf(move, _("Illegal move: %d.%s%s"),
8809                     (forwardMostMove / 2) + 1,
8810                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8811             DisplayError(move, 0);
8812             done = TRUE;
8813         } else {
8814             if (appData.debugMode)
8815               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8816                       yy_text, currentMoveString);
8817             fromX = currentMoveString[0] - AAA;
8818             fromY = currentMoveString[1] - ONE;
8819             toX = currentMoveString[2] - AAA;
8820             toY = currentMoveString[3] - ONE;
8821             promoChar = currentMoveString[4];
8822         }
8823         break;
8824
8825       case AmbiguousMove:
8826         if (appData.debugMode)
8827           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8828         sprintf(move, _("Ambiguous move: %d.%s%s"),
8829                 (forwardMostMove / 2) + 1,
8830                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8831         DisplayError(move, 0);
8832         done = TRUE;
8833         break;
8834
8835       default:
8836       case ImpossibleMove:
8837         if (appData.debugMode)
8838           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8839         sprintf(move, _("Illegal move: %d.%s%s"),
8840                 (forwardMostMove / 2) + 1,
8841                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8842         DisplayError(move, 0);
8843         done = TRUE;
8844         break;
8845     }
8846
8847     if (done) {
8848         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8849             DrawPosition(FALSE, boards[currentMove]);
8850             DisplayBothClocks();
8851             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8852               DisplayComment(currentMove - 1, commentList[currentMove]);
8853         }
8854         (void) StopLoadGameTimer();
8855         gameFileFP = NULL;
8856         cmailOldMove = forwardMostMove;
8857         return FALSE;
8858     } else {
8859         /* currentMoveString is set as a side-effect of yylex */
8860         strcat(currentMoveString, "\n");
8861         strcpy(moveList[forwardMostMove], currentMoveString);
8862         
8863         thinkOutput[0] = NULLCHAR;
8864         MakeMove(fromX, fromY, toX, toY, promoChar);
8865         currentMove = forwardMostMove;
8866         return TRUE;
8867     }
8868 }
8869
8870 /* Load the nth game from the given file */
8871 int
8872 LoadGameFromFile(filename, n, title, useList)
8873      char *filename;
8874      int n;
8875      char *title;
8876      /*Boolean*/ int useList;
8877 {
8878     FILE *f;
8879     char buf[MSG_SIZ];
8880
8881     if (strcmp(filename, "-") == 0) {
8882         f = stdin;
8883         title = "stdin";
8884     } else {
8885         f = fopen(filename, "rb");
8886         if (f == NULL) {
8887           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8888             DisplayError(buf, errno);
8889             return FALSE;
8890         }
8891     }
8892     if (fseek(f, 0, 0) == -1) {
8893         /* f is not seekable; probably a pipe */
8894         useList = FALSE;
8895     }
8896     if (useList && n == 0) {
8897         int error = GameListBuild(f);
8898         if (error) {
8899             DisplayError(_("Cannot build game list"), error);
8900         } else if (!ListEmpty(&gameList) &&
8901                    ((ListGame *) gameList.tailPred)->number > 1) {
8902             GameListPopUp(f, title);
8903             return TRUE;
8904         }
8905         GameListDestroy();
8906         n = 1;
8907     }
8908     if (n == 0) n = 1;
8909     return LoadGame(f, n, title, FALSE);
8910 }
8911
8912
8913 void
8914 MakeRegisteredMove()
8915 {
8916     int fromX, fromY, toX, toY;
8917     char promoChar;
8918     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8919         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8920           case CMAIL_MOVE:
8921           case CMAIL_DRAW:
8922             if (appData.debugMode)
8923               fprintf(debugFP, "Restoring %s for game %d\n",
8924                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8925     
8926             thinkOutput[0] = NULLCHAR;
8927             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8928             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8929             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8930             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8931             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8932             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8933             MakeMove(fromX, fromY, toX, toY, promoChar);
8934             ShowMove(fromX, fromY, toX, toY);
8935               
8936             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8937               case MT_NONE:
8938               case MT_CHECK:
8939                 break;
8940                 
8941               case MT_CHECKMATE:
8942               case MT_STAINMATE:
8943                 if (WhiteOnMove(currentMove)) {
8944                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8945                 } else {
8946                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8947                 }
8948                 break;
8949                 
8950               case MT_STALEMATE:
8951                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8952                 break;
8953             }
8954
8955             break;
8956             
8957           case CMAIL_RESIGN:
8958             if (WhiteOnMove(currentMove)) {
8959                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8960             } else {
8961                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8962             }
8963             break;
8964             
8965           case CMAIL_ACCEPT:
8966             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8967             break;
8968               
8969           default:
8970             break;
8971         }
8972     }
8973
8974     return;
8975 }
8976
8977 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8978 int
8979 CmailLoadGame(f, gameNumber, title, useList)
8980      FILE *f;
8981      int gameNumber;
8982      char *title;
8983      int useList;
8984 {
8985     int retVal;
8986
8987     if (gameNumber > nCmailGames) {
8988         DisplayError(_("No more games in this message"), 0);
8989         return FALSE;
8990     }
8991     if (f == lastLoadGameFP) {
8992         int offset = gameNumber - lastLoadGameNumber;
8993         if (offset == 0) {
8994             cmailMsg[0] = NULLCHAR;
8995             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8996                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8997                 nCmailMovesRegistered--;
8998             }
8999             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9000             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9001                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9002             }
9003         } else {
9004             if (! RegisterMove()) return FALSE;
9005         }
9006     }
9007
9008     retVal = LoadGame(f, gameNumber, title, useList);
9009
9010     /* Make move registered during previous look at this game, if any */
9011     MakeRegisteredMove();
9012
9013     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9014         commentList[currentMove]
9015           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9016         DisplayComment(currentMove - 1, commentList[currentMove]);
9017     }
9018
9019     return retVal;
9020 }
9021
9022 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9023 int
9024 ReloadGame(offset)
9025      int offset;
9026 {
9027     int gameNumber = lastLoadGameNumber + offset;
9028     if (lastLoadGameFP == NULL) {
9029         DisplayError(_("No game has been loaded yet"), 0);
9030         return FALSE;
9031     }
9032     if (gameNumber <= 0) {
9033         DisplayError(_("Can't back up any further"), 0);
9034         return FALSE;
9035     }
9036     if (cmailMsgLoaded) {
9037         return CmailLoadGame(lastLoadGameFP, gameNumber,
9038                              lastLoadGameTitle, lastLoadGameUseList);
9039     } else {
9040         return LoadGame(lastLoadGameFP, gameNumber,
9041                         lastLoadGameTitle, lastLoadGameUseList);
9042     }
9043 }
9044
9045
9046
9047 /* Load the nth game from open file f */
9048 int
9049 LoadGame(f, gameNumber, title, useList)
9050      FILE *f;
9051      int gameNumber;
9052      char *title;
9053      int useList;
9054 {
9055     ChessMove cm;
9056     char buf[MSG_SIZ];
9057     int gn = gameNumber;
9058     ListGame *lg = NULL;
9059     int numPGNTags = 0;
9060     int err;
9061     GameMode oldGameMode;
9062     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9063
9064     if (appData.debugMode) 
9065         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9066
9067     if (gameMode == Training )
9068         SetTrainingModeOff();
9069
9070     oldGameMode = gameMode;
9071     if (gameMode != BeginningOfGame) {
9072       Reset(FALSE, TRUE);
9073     }
9074
9075     gameFileFP = f;
9076     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9077         fclose(lastLoadGameFP);
9078     }
9079
9080     if (useList) {
9081         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9082         
9083         if (lg) {
9084             fseek(f, lg->offset, 0);
9085             GameListHighlight(gameNumber);
9086             gn = 1;
9087         }
9088         else {
9089             DisplayError(_("Game number out of range"), 0);
9090             return FALSE;
9091         }
9092     } else {
9093         GameListDestroy();
9094         if (fseek(f, 0, 0) == -1) {
9095             if (f == lastLoadGameFP ?
9096                 gameNumber == lastLoadGameNumber + 1 :
9097                 gameNumber == 1) {
9098                 gn = 1;
9099             } else {
9100                 DisplayError(_("Can't seek on game file"), 0);
9101                 return FALSE;
9102             }
9103         }
9104     }
9105     lastLoadGameFP = f;
9106     lastLoadGameNumber = gameNumber;
9107     strcpy(lastLoadGameTitle, title);
9108     lastLoadGameUseList = useList;
9109
9110     yynewfile(f);
9111
9112     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9113       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9114                 lg->gameInfo.black);
9115             DisplayTitle(buf);
9116     } else if (*title != NULLCHAR) {
9117         if (gameNumber > 1) {
9118             sprintf(buf, "%s %d", title, gameNumber);
9119             DisplayTitle(buf);
9120         } else {
9121             DisplayTitle(title);
9122         }
9123     }
9124
9125     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9126         gameMode = PlayFromGameFile;
9127         ModeHighlight();
9128     }
9129
9130     currentMove = forwardMostMove = backwardMostMove = 0;
9131     CopyBoard(boards[0], initialPosition);
9132     StopClocks();
9133
9134     /*
9135      * Skip the first gn-1 games in the file.
9136      * Also skip over anything that precedes an identifiable 
9137      * start of game marker, to avoid being confused by 
9138      * garbage at the start of the file.  Currently 
9139      * recognized start of game markers are the move number "1",
9140      * the pattern "gnuchess .* game", the pattern
9141      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9142      * A game that starts with one of the latter two patterns
9143      * will also have a move number 1, possibly
9144      * following a position diagram.
9145      * 5-4-02: Let's try being more lenient and allowing a game to
9146      * start with an unnumbered move.  Does that break anything?
9147      */
9148     cm = lastLoadGameStart = (ChessMove) 0;
9149     while (gn > 0) {
9150         yyboardindex = forwardMostMove;
9151         cm = (ChessMove) yylex();
9152         switch (cm) {
9153           case (ChessMove) 0:
9154             if (cmailMsgLoaded) {
9155                 nCmailGames = CMAIL_MAX_GAMES - gn;
9156             } else {
9157                 Reset(TRUE, TRUE);
9158                 DisplayError(_("Game not found in file"), 0);
9159             }
9160             return FALSE;
9161
9162           case GNUChessGame:
9163           case XBoardGame:
9164             gn--;
9165             lastLoadGameStart = cm;
9166             break;
9167             
9168           case MoveNumberOne:
9169             switch (lastLoadGameStart) {
9170               case GNUChessGame:
9171               case XBoardGame:
9172               case PGNTag:
9173                 break;
9174               case MoveNumberOne:
9175               case (ChessMove) 0:
9176                 gn--;           /* count this game */
9177                 lastLoadGameStart = cm;
9178                 break;
9179               default:
9180                 /* impossible */
9181                 break;
9182             }
9183             break;
9184
9185           case PGNTag:
9186             switch (lastLoadGameStart) {
9187               case GNUChessGame:
9188               case PGNTag:
9189               case MoveNumberOne:
9190               case (ChessMove) 0:
9191                 gn--;           /* count this game */
9192                 lastLoadGameStart = cm;
9193                 break;
9194               case XBoardGame:
9195                 lastLoadGameStart = cm; /* game counted already */
9196                 break;
9197               default:
9198                 /* impossible */
9199                 break;
9200             }
9201             if (gn > 0) {
9202                 do {
9203                     yyboardindex = forwardMostMove;
9204                     cm = (ChessMove) yylex();
9205                 } while (cm == PGNTag || cm == Comment);
9206             }
9207             break;
9208
9209           case WhiteWins:
9210           case BlackWins:
9211           case GameIsDrawn:
9212             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9213                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9214                     != CMAIL_OLD_RESULT) {
9215                     nCmailResults ++ ;
9216                     cmailResult[  CMAIL_MAX_GAMES
9217                                 - gn - 1] = CMAIL_OLD_RESULT;
9218                 }
9219             }
9220             break;
9221
9222           case NormalMove:
9223             /* Only a NormalMove can be at the start of a game
9224              * without a position diagram. */
9225             if (lastLoadGameStart == (ChessMove) 0) {
9226               gn--;
9227               lastLoadGameStart = MoveNumberOne;
9228             }
9229             break;
9230
9231           default:
9232             break;
9233         }
9234     }
9235     
9236     if (appData.debugMode)
9237       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9238
9239     if (cm == XBoardGame) {
9240         /* Skip any header junk before position diagram and/or move 1 */
9241         for (;;) {
9242             yyboardindex = forwardMostMove;
9243             cm = (ChessMove) yylex();
9244
9245             if (cm == (ChessMove) 0 ||
9246                 cm == GNUChessGame || cm == XBoardGame) {
9247                 /* Empty game; pretend end-of-file and handle later */
9248                 cm = (ChessMove) 0;
9249                 break;
9250             }
9251
9252             if (cm == MoveNumberOne || cm == PositionDiagram ||
9253                 cm == PGNTag || cm == Comment)
9254               break;
9255         }
9256     } else if (cm == GNUChessGame) {
9257         if (gameInfo.event != NULL) {
9258             free(gameInfo.event);
9259         }
9260         gameInfo.event = StrSave(yy_text);
9261     }   
9262
9263     startedFromSetupPosition = FALSE;
9264     while (cm == PGNTag) {
9265         if (appData.debugMode) 
9266           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9267         err = ParsePGNTag(yy_text, &gameInfo);
9268         if (!err) numPGNTags++;
9269
9270         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9271         if(gameInfo.variant != oldVariant) {
9272             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9273             InitPosition(TRUE);
9274             oldVariant = gameInfo.variant;
9275             if (appData.debugMode) 
9276               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9277         }
9278
9279
9280         if (gameInfo.fen != NULL) {
9281           Board initial_position;
9282           startedFromSetupPosition = TRUE;
9283           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9284             Reset(TRUE, TRUE);
9285             DisplayError(_("Bad FEN position in file"), 0);
9286             return FALSE;
9287           }
9288           CopyBoard(boards[0], initial_position);
9289           if (blackPlaysFirst) {
9290             currentMove = forwardMostMove = backwardMostMove = 1;
9291             CopyBoard(boards[1], initial_position);
9292             strcpy(moveList[0], "");
9293             strcpy(parseList[0], "");
9294             timeRemaining[0][1] = whiteTimeRemaining;
9295             timeRemaining[1][1] = blackTimeRemaining;
9296             if (commentList[0] != NULL) {
9297               commentList[1] = commentList[0];
9298               commentList[0] = NULL;
9299             }
9300           } else {
9301             currentMove = forwardMostMove = backwardMostMove = 0;
9302           }
9303           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9304           {   int i;
9305               initialRulePlies = FENrulePlies;
9306               for( i=0; i< nrCastlingRights; i++ )
9307                   initialRights[i] = initial_position[CASTLING][i];
9308           }
9309           yyboardindex = forwardMostMove;
9310           free(gameInfo.fen);
9311           gameInfo.fen = NULL;
9312         }
9313
9314         yyboardindex = forwardMostMove;
9315         cm = (ChessMove) yylex();
9316
9317         /* Handle comments interspersed among the tags */
9318         while (cm == Comment) {
9319             char *p;
9320             if (appData.debugMode) 
9321               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9322             p = yy_text;
9323             AppendComment(currentMove, p, FALSE);
9324             yyboardindex = forwardMostMove;
9325             cm = (ChessMove) yylex();
9326         }
9327     }
9328
9329     /* don't rely on existence of Event tag since if game was
9330      * pasted from clipboard the Event tag may not exist
9331      */
9332     if (numPGNTags > 0){
9333         char *tags;
9334         if (gameInfo.variant == VariantNormal) {
9335           gameInfo.variant = StringToVariant(gameInfo.event);
9336         }
9337         if (!matchMode) {
9338           if( appData.autoDisplayTags ) {
9339             tags = PGNTags(&gameInfo);
9340             TagsPopUp(tags, CmailMsg());
9341             free(tags);
9342           }
9343         }
9344     } else {
9345         /* Make something up, but don't display it now */
9346         SetGameInfo();
9347         TagsPopDown();
9348     }
9349
9350     if (cm == PositionDiagram) {
9351         int i, j;
9352         char *p;
9353         Board initial_position;
9354
9355         if (appData.debugMode)
9356           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9357
9358         if (!startedFromSetupPosition) {
9359             p = yy_text;
9360             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9361               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9362                 switch (*p) {
9363                   case '[':
9364                   case '-':
9365                   case ' ':
9366                   case '\t':
9367                   case '\n':
9368                   case '\r':
9369                     break;
9370                   default:
9371                     initial_position[i][j++] = CharToPiece(*p);
9372                     break;
9373                 }
9374             while (*p == ' ' || *p == '\t' ||
9375                    *p == '\n' || *p == '\r') p++;
9376         
9377             if (strncmp(p, "black", strlen("black"))==0)
9378               blackPlaysFirst = TRUE;
9379             else
9380               blackPlaysFirst = FALSE;
9381             startedFromSetupPosition = TRUE;
9382         
9383             CopyBoard(boards[0], initial_position);
9384             if (blackPlaysFirst) {
9385                 currentMove = forwardMostMove = backwardMostMove = 1;
9386                 CopyBoard(boards[1], initial_position);
9387                 strcpy(moveList[0], "");
9388                 strcpy(parseList[0], "");
9389                 timeRemaining[0][1] = whiteTimeRemaining;
9390                 timeRemaining[1][1] = blackTimeRemaining;
9391                 if (commentList[0] != NULL) {
9392                     commentList[1] = commentList[0];
9393                     commentList[0] = NULL;
9394                 }
9395             } else {
9396                 currentMove = forwardMostMove = backwardMostMove = 0;
9397             }
9398         }
9399         yyboardindex = forwardMostMove;
9400         cm = (ChessMove) yylex();
9401     }
9402
9403     if (first.pr == NoProc) {
9404         StartChessProgram(&first);
9405     }
9406     InitChessProgram(&first, FALSE);
9407     SendToProgram("force\n", &first);
9408     if (startedFromSetupPosition) {
9409         SendBoard(&first, forwardMostMove);
9410     if (appData.debugMode) {
9411         fprintf(debugFP, "Load Game\n");
9412     }
9413         DisplayBothClocks();
9414     }      
9415
9416     /* [HGM] server: flag to write setup moves in broadcast file as one */
9417     loadFlag = appData.suppressLoadMoves;
9418
9419     while (cm == Comment) {
9420         char *p;
9421         if (appData.debugMode) 
9422           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9423         p = yy_text;
9424         AppendComment(currentMove, p, FALSE);
9425         yyboardindex = forwardMostMove;
9426         cm = (ChessMove) yylex();
9427     }
9428
9429     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9430         cm == WhiteWins || cm == BlackWins ||
9431         cm == GameIsDrawn || cm == GameUnfinished) {
9432         DisplayMessage("", _("No moves in game"));
9433         if (cmailMsgLoaded) {
9434             if (appData.debugMode)
9435               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9436             ClearHighlights();
9437             flipView = FALSE;
9438         }
9439         DrawPosition(FALSE, boards[currentMove]);
9440         DisplayBothClocks();
9441         gameMode = EditGame;
9442         ModeHighlight();
9443         gameFileFP = NULL;
9444         cmailOldMove = 0;
9445         return TRUE;
9446     }
9447
9448     // [HGM] PV info: routine tests if comment empty
9449     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9450         DisplayComment(currentMove - 1, commentList[currentMove]);
9451     }
9452     if (!matchMode && appData.timeDelay != 0) 
9453       DrawPosition(FALSE, boards[currentMove]);
9454
9455     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9456       programStats.ok_to_send = 1;
9457     }
9458
9459     /* if the first token after the PGN tags is a move
9460      * and not move number 1, retrieve it from the parser 
9461      */
9462     if (cm != MoveNumberOne)
9463         LoadGameOneMove(cm);
9464
9465     /* load the remaining moves from the file */
9466     while (LoadGameOneMove((ChessMove)0)) {
9467       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9468       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9469     }
9470
9471     /* rewind to the start of the game */
9472     currentMove = backwardMostMove;
9473
9474     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9475
9476     if (oldGameMode == AnalyzeFile ||
9477         oldGameMode == AnalyzeMode) {
9478       AnalyzeFileEvent();
9479     }
9480
9481     if (matchMode || appData.timeDelay == 0) {
9482       ToEndEvent();
9483       gameMode = EditGame;
9484       ModeHighlight();
9485     } else if (appData.timeDelay > 0) {
9486       AutoPlayGameLoop();
9487     }
9488
9489     if (appData.debugMode) 
9490         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9491
9492     loadFlag = 0; /* [HGM] true game starts */
9493     return TRUE;
9494 }
9495
9496 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9497 int
9498 ReloadPosition(offset)
9499      int offset;
9500 {
9501     int positionNumber = lastLoadPositionNumber + offset;
9502     if (lastLoadPositionFP == NULL) {
9503         DisplayError(_("No position has been loaded yet"), 0);
9504         return FALSE;
9505     }
9506     if (positionNumber <= 0) {
9507         DisplayError(_("Can't back up any further"), 0);
9508         return FALSE;
9509     }
9510     return LoadPosition(lastLoadPositionFP, positionNumber,
9511                         lastLoadPositionTitle);
9512 }
9513
9514 /* Load the nth position from the given file */
9515 int
9516 LoadPositionFromFile(filename, n, title)
9517      char *filename;
9518      int n;
9519      char *title;
9520 {
9521     FILE *f;
9522     char buf[MSG_SIZ];
9523
9524     if (strcmp(filename, "-") == 0) {
9525         return LoadPosition(stdin, n, "stdin");
9526     } else {
9527         f = fopen(filename, "rb");
9528         if (f == NULL) {
9529             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9530             DisplayError(buf, errno);
9531             return FALSE;
9532         } else {
9533             return LoadPosition(f, n, title);
9534         }
9535     }
9536 }
9537
9538 /* Load the nth position from the given open file, and close it */
9539 int
9540 LoadPosition(f, positionNumber, title)
9541      FILE *f;
9542      int positionNumber;
9543      char *title;
9544 {
9545     char *p, line[MSG_SIZ];
9546     Board initial_position;
9547     int i, j, fenMode, pn;
9548     
9549     if (gameMode == Training )
9550         SetTrainingModeOff();
9551
9552     if (gameMode != BeginningOfGame) {
9553         Reset(FALSE, TRUE);
9554     }
9555     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9556         fclose(lastLoadPositionFP);
9557     }
9558     if (positionNumber == 0) positionNumber = 1;
9559     lastLoadPositionFP = f;
9560     lastLoadPositionNumber = positionNumber;
9561     strcpy(lastLoadPositionTitle, title);
9562     if (first.pr == NoProc) {
9563       StartChessProgram(&first);
9564       InitChessProgram(&first, FALSE);
9565     }    
9566     pn = positionNumber;
9567     if (positionNumber < 0) {
9568         /* Negative position number means to seek to that byte offset */
9569         if (fseek(f, -positionNumber, 0) == -1) {
9570             DisplayError(_("Can't seek on position file"), 0);
9571             return FALSE;
9572         };
9573         pn = 1;
9574     } else {
9575         if (fseek(f, 0, 0) == -1) {
9576             if (f == lastLoadPositionFP ?
9577                 positionNumber == lastLoadPositionNumber + 1 :
9578                 positionNumber == 1) {
9579                 pn = 1;
9580             } else {
9581                 DisplayError(_("Can't seek on position file"), 0);
9582                 return FALSE;
9583             }
9584         }
9585     }
9586     /* See if this file is FEN or old-style xboard */
9587     if (fgets(line, MSG_SIZ, f) == NULL) {
9588         DisplayError(_("Position not found in file"), 0);
9589         return FALSE;
9590     }
9591     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9592     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9593
9594     if (pn >= 2) {
9595         if (fenMode || line[0] == '#') pn--;
9596         while (pn > 0) {
9597             /* skip positions before number pn */
9598             if (fgets(line, MSG_SIZ, f) == NULL) {
9599                 Reset(TRUE, TRUE);
9600                 DisplayError(_("Position not found in file"), 0);
9601                 return FALSE;
9602             }
9603             if (fenMode || line[0] == '#') pn--;
9604         }
9605     }
9606
9607     if (fenMode) {
9608         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9609             DisplayError(_("Bad FEN position in file"), 0);
9610             return FALSE;
9611         }
9612     } else {
9613         (void) fgets(line, MSG_SIZ, f);
9614         (void) fgets(line, MSG_SIZ, f);
9615     
9616         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9617             (void) fgets(line, MSG_SIZ, f);
9618             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9619                 if (*p == ' ')
9620                   continue;
9621                 initial_position[i][j++] = CharToPiece(*p);
9622             }
9623         }
9624     
9625         blackPlaysFirst = FALSE;
9626         if (!feof(f)) {
9627             (void) fgets(line, MSG_SIZ, f);
9628             if (strncmp(line, "black", strlen("black"))==0)
9629               blackPlaysFirst = TRUE;
9630         }
9631     }
9632     startedFromSetupPosition = TRUE;
9633     
9634     SendToProgram("force\n", &first);
9635     CopyBoard(boards[0], initial_position);
9636     if (blackPlaysFirst) {
9637         currentMove = forwardMostMove = backwardMostMove = 1;
9638         strcpy(moveList[0], "");
9639         strcpy(parseList[0], "");
9640         CopyBoard(boards[1], initial_position);
9641         DisplayMessage("", _("Black to play"));
9642     } else {
9643         currentMove = forwardMostMove = backwardMostMove = 0;
9644         DisplayMessage("", _("White to play"));
9645     }
9646     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9647     SendBoard(&first, forwardMostMove);
9648     if (appData.debugMode) {
9649 int i, j;
9650   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9651   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9652         fprintf(debugFP, "Load Position\n");
9653     }
9654
9655     if (positionNumber > 1) {
9656         sprintf(line, "%s %d", title, positionNumber);
9657         DisplayTitle(line);
9658     } else {
9659         DisplayTitle(title);
9660     }
9661     gameMode = EditGame;
9662     ModeHighlight();
9663     ResetClocks();
9664     timeRemaining[0][1] = whiteTimeRemaining;
9665     timeRemaining[1][1] = blackTimeRemaining;
9666     DrawPosition(FALSE, boards[currentMove]);
9667    
9668     return TRUE;
9669 }
9670
9671
9672 void
9673 CopyPlayerNameIntoFileName(dest, src)
9674      char **dest, *src;
9675 {
9676     while (*src != NULLCHAR && *src != ',') {
9677         if (*src == ' ') {
9678             *(*dest)++ = '_';
9679             src++;
9680         } else {
9681             *(*dest)++ = *src++;
9682         }
9683     }
9684 }
9685
9686 char *DefaultFileName(ext)
9687      char *ext;
9688 {
9689     static char def[MSG_SIZ];
9690     char *p;
9691
9692     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9693         p = def;
9694         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9695         *p++ = '-';
9696         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9697         *p++ = '.';
9698         strcpy(p, ext);
9699     } else {
9700         def[0] = NULLCHAR;
9701     }
9702     return def;
9703 }
9704
9705 /* Save the current game to the given file */
9706 int
9707 SaveGameToFile(filename, append)
9708      char *filename;
9709      int append;
9710 {
9711     FILE *f;
9712     char buf[MSG_SIZ];
9713
9714     if (strcmp(filename, "-") == 0) {
9715         return SaveGame(stdout, 0, NULL);
9716     } else {
9717         f = fopen(filename, append ? "a" : "w");
9718         if (f == NULL) {
9719             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9720             DisplayError(buf, errno);
9721             return FALSE;
9722         } else {
9723             return SaveGame(f, 0, NULL);
9724         }
9725     }
9726 }
9727
9728 char *
9729 SavePart(str)
9730      char *str;
9731 {
9732     static char buf[MSG_SIZ];
9733     char *p;
9734     
9735     p = strchr(str, ' ');
9736     if (p == NULL) return str;
9737     strncpy(buf, str, p - str);
9738     buf[p - str] = NULLCHAR;
9739     return buf;
9740 }
9741
9742 #define PGN_MAX_LINE 75
9743
9744 #define PGN_SIDE_WHITE  0
9745 #define PGN_SIDE_BLACK  1
9746
9747 /* [AS] */
9748 static int FindFirstMoveOutOfBook( int side )
9749 {
9750     int result = -1;
9751
9752     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9753         int index = backwardMostMove;
9754         int has_book_hit = 0;
9755
9756         if( (index % 2) != side ) {
9757             index++;
9758         }
9759
9760         while( index < forwardMostMove ) {
9761             /* Check to see if engine is in book */
9762             int depth = pvInfoList[index].depth;
9763             int score = pvInfoList[index].score;
9764             int in_book = 0;
9765
9766             if( depth <= 2 ) {
9767                 in_book = 1;
9768             }
9769             else if( score == 0 && depth == 63 ) {
9770                 in_book = 1; /* Zappa */
9771             }
9772             else if( score == 2 && depth == 99 ) {
9773                 in_book = 1; /* Abrok */
9774             }
9775
9776             has_book_hit += in_book;
9777
9778             if( ! in_book ) {
9779                 result = index;
9780
9781                 break;
9782             }
9783
9784             index += 2;
9785         }
9786     }
9787
9788     return result;
9789 }
9790
9791 /* [AS] */
9792 void GetOutOfBookInfo( char * buf )
9793 {
9794     int oob[2];
9795     int i;
9796     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9797
9798     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9799     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9800
9801     *buf = '\0';
9802
9803     if( oob[0] >= 0 || oob[1] >= 0 ) {
9804         for( i=0; i<2; i++ ) {
9805             int idx = oob[i];
9806
9807             if( idx >= 0 ) {
9808                 if( i > 0 && oob[0] >= 0 ) {
9809                     strcat( buf, "   " );
9810                 }
9811
9812                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9813                 sprintf( buf+strlen(buf), "%s%.2f", 
9814                     pvInfoList[idx].score >= 0 ? "+" : "",
9815                     pvInfoList[idx].score / 100.0 );
9816             }
9817         }
9818     }
9819 }
9820
9821 /* Save game in PGN style and close the file */
9822 int
9823 SaveGamePGN(f)
9824      FILE *f;
9825 {
9826     int i, offset, linelen, newblock;
9827     time_t tm;
9828 //    char *movetext;
9829     char numtext[32];
9830     int movelen, numlen, blank;
9831     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9832
9833     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9834     
9835     tm = time((time_t *) NULL);
9836     
9837     PrintPGNTags(f, &gameInfo);
9838     
9839     if (backwardMostMove > 0 || startedFromSetupPosition) {
9840         char *fen = PositionToFEN(backwardMostMove, NULL);
9841         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9842         fprintf(f, "\n{--------------\n");
9843         PrintPosition(f, backwardMostMove);
9844         fprintf(f, "--------------}\n");
9845         free(fen);
9846     }
9847     else {
9848         /* [AS] Out of book annotation */
9849         if( appData.saveOutOfBookInfo ) {
9850             char buf[64];
9851
9852             GetOutOfBookInfo( buf );
9853
9854             if( buf[0] != '\0' ) {
9855                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9856             }
9857         }
9858
9859         fprintf(f, "\n");
9860     }
9861
9862     i = backwardMostMove;
9863     linelen = 0;
9864     newblock = TRUE;
9865
9866     while (i < forwardMostMove) {
9867         /* Print comments preceding this move */
9868         if (commentList[i] != NULL) {
9869             if (linelen > 0) fprintf(f, "\n");
9870             fprintf(f, "%s", commentList[i]);
9871             linelen = 0;
9872             newblock = TRUE;
9873         }
9874
9875         /* Format move number */
9876         if ((i % 2) == 0) {
9877             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9878         } else {
9879             if (newblock) {
9880                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9881             } else {
9882                 numtext[0] = NULLCHAR;
9883             }
9884         }
9885         numlen = strlen(numtext);
9886         newblock = FALSE;
9887
9888         /* Print move number */
9889         blank = linelen > 0 && numlen > 0;
9890         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9891             fprintf(f, "\n");
9892             linelen = 0;
9893             blank = 0;
9894         }
9895         if (blank) {
9896             fprintf(f, " ");
9897             linelen++;
9898         }
9899         fprintf(f, "%s", numtext);
9900         linelen += numlen;
9901
9902         /* Get move */
9903         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9904         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9905
9906         /* Print move */
9907         blank = linelen > 0 && movelen > 0;
9908         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9909             fprintf(f, "\n");
9910             linelen = 0;
9911             blank = 0;
9912         }
9913         if (blank) {
9914             fprintf(f, " ");
9915             linelen++;
9916         }
9917         fprintf(f, "%s", move_buffer);
9918         linelen += movelen;
9919
9920         /* [AS] Add PV info if present */
9921         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9922             /* [HGM] add time */
9923             char buf[MSG_SIZ]; int seconds;
9924
9925             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9926
9927             if( seconds <= 0) buf[0] = 0; else
9928             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9929                 seconds = (seconds + 4)/10; // round to full seconds
9930                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9931                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9932             }
9933
9934             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9935                 pvInfoList[i].score >= 0 ? "+" : "",
9936                 pvInfoList[i].score / 100.0,
9937                 pvInfoList[i].depth,
9938                 buf );
9939
9940             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9941
9942             /* Print score/depth */
9943             blank = linelen > 0 && movelen > 0;
9944             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9945                 fprintf(f, "\n");
9946                 linelen = 0;
9947                 blank = 0;
9948             }
9949             if (blank) {
9950                 fprintf(f, " ");
9951                 linelen++;
9952             }
9953             fprintf(f, "%s", move_buffer);
9954             linelen += movelen;
9955         }
9956
9957         i++;
9958     }
9959     
9960     /* Start a new line */
9961     if (linelen > 0) fprintf(f, "\n");
9962
9963     /* Print comments after last move */
9964     if (commentList[i] != NULL) {
9965         fprintf(f, "%s\n", commentList[i]);
9966     }
9967
9968     /* Print result */
9969     if (gameInfo.resultDetails != NULL &&
9970         gameInfo.resultDetails[0] != NULLCHAR) {
9971         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9972                 PGNResult(gameInfo.result));
9973     } else {
9974         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9975     }
9976
9977     fclose(f);
9978     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9979     return TRUE;
9980 }
9981
9982 /* Save game in old style and close the file */
9983 int
9984 SaveGameOldStyle(f)
9985      FILE *f;
9986 {
9987     int i, offset;
9988     time_t tm;
9989     
9990     tm = time((time_t *) NULL);
9991     
9992     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9993     PrintOpponents(f);
9994     
9995     if (backwardMostMove > 0 || startedFromSetupPosition) {
9996         fprintf(f, "\n[--------------\n");
9997         PrintPosition(f, backwardMostMove);
9998         fprintf(f, "--------------]\n");
9999     } else {
10000         fprintf(f, "\n");
10001     }
10002
10003     i = backwardMostMove;
10004     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10005
10006     while (i < forwardMostMove) {
10007         if (commentList[i] != NULL) {
10008             fprintf(f, "[%s]\n", commentList[i]);
10009         }
10010
10011         if ((i % 2) == 1) {
10012             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10013             i++;
10014         } else {
10015             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10016             i++;
10017             if (commentList[i] != NULL) {
10018                 fprintf(f, "\n");
10019                 continue;
10020             }
10021             if (i >= forwardMostMove) {
10022                 fprintf(f, "\n");
10023                 break;
10024             }
10025             fprintf(f, "%s\n", parseList[i]);
10026             i++;
10027         }
10028     }
10029     
10030     if (commentList[i] != NULL) {
10031         fprintf(f, "[%s]\n", commentList[i]);
10032     }
10033
10034     /* This isn't really the old style, but it's close enough */
10035     if (gameInfo.resultDetails != NULL &&
10036         gameInfo.resultDetails[0] != NULLCHAR) {
10037         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10038                 gameInfo.resultDetails);
10039     } else {
10040         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10041     }
10042
10043     fclose(f);
10044     return TRUE;
10045 }
10046
10047 /* Save the current game to open file f and close the file */
10048 int
10049 SaveGame(f, dummy, dummy2)
10050      FILE *f;
10051      int dummy;
10052      char *dummy2;
10053 {
10054     if (gameMode == EditPosition) EditPositionDone(TRUE);
10055     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10056     if (appData.oldSaveStyle)
10057       return SaveGameOldStyle(f);
10058     else
10059       return SaveGamePGN(f);
10060 }
10061
10062 /* Save the current position to the given file */
10063 int
10064 SavePositionToFile(filename)
10065      char *filename;
10066 {
10067     FILE *f;
10068     char buf[MSG_SIZ];
10069
10070     if (strcmp(filename, "-") == 0) {
10071         return SavePosition(stdout, 0, NULL);
10072     } else {
10073         f = fopen(filename, "a");
10074         if (f == NULL) {
10075             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10076             DisplayError(buf, errno);
10077             return FALSE;
10078         } else {
10079             SavePosition(f, 0, NULL);
10080             return TRUE;
10081         }
10082     }
10083 }
10084
10085 /* Save the current position to the given open file and close the file */
10086 int
10087 SavePosition(f, dummy, dummy2)
10088      FILE *f;
10089      int dummy;
10090      char *dummy2;
10091 {
10092     time_t tm;
10093     char *fen;
10094     
10095     if (gameMode == EditPosition) EditPositionDone(TRUE);
10096     if (appData.oldSaveStyle) {
10097         tm = time((time_t *) NULL);
10098     
10099         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10100         PrintOpponents(f);
10101         fprintf(f, "[--------------\n");
10102         PrintPosition(f, currentMove);
10103         fprintf(f, "--------------]\n");
10104     } else {
10105         fen = PositionToFEN(currentMove, NULL);
10106         fprintf(f, "%s\n", fen);
10107         free(fen);
10108     }
10109     fclose(f);
10110     return TRUE;
10111 }
10112
10113 void
10114 ReloadCmailMsgEvent(unregister)
10115      int unregister;
10116 {
10117 #if !WIN32
10118     static char *inFilename = NULL;
10119     static char *outFilename;
10120     int i;
10121     struct stat inbuf, outbuf;
10122     int status;
10123     
10124     /* Any registered moves are unregistered if unregister is set, */
10125     /* i.e. invoked by the signal handler */
10126     if (unregister) {
10127         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10128             cmailMoveRegistered[i] = FALSE;
10129             if (cmailCommentList[i] != NULL) {
10130                 free(cmailCommentList[i]);
10131                 cmailCommentList[i] = NULL;
10132             }
10133         }
10134         nCmailMovesRegistered = 0;
10135     }
10136
10137     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10138         cmailResult[i] = CMAIL_NOT_RESULT;
10139     }
10140     nCmailResults = 0;
10141
10142     if (inFilename == NULL) {
10143         /* Because the filenames are static they only get malloced once  */
10144         /* and they never get freed                                      */
10145         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10146         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10147
10148         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10149         sprintf(outFilename, "%s.out", appData.cmailGameName);
10150     }
10151     
10152     status = stat(outFilename, &outbuf);
10153     if (status < 0) {
10154         cmailMailedMove = FALSE;
10155     } else {
10156         status = stat(inFilename, &inbuf);
10157         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10158     }
10159     
10160     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10161        counts the games, notes how each one terminated, etc.
10162        
10163        It would be nice to remove this kludge and instead gather all
10164        the information while building the game list.  (And to keep it
10165        in the game list nodes instead of having a bunch of fixed-size
10166        parallel arrays.)  Note this will require getting each game's
10167        termination from the PGN tags, as the game list builder does
10168        not process the game moves.  --mann
10169        */
10170     cmailMsgLoaded = TRUE;
10171     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10172     
10173     /* Load first game in the file or popup game menu */
10174     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10175
10176 #endif /* !WIN32 */
10177     return;
10178 }
10179
10180 int
10181 RegisterMove()
10182 {
10183     FILE *f;
10184     char string[MSG_SIZ];
10185
10186     if (   cmailMailedMove
10187         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10188         return TRUE;            /* Allow free viewing  */
10189     }
10190
10191     /* Unregister move to ensure that we don't leave RegisterMove        */
10192     /* with the move registered when the conditions for registering no   */
10193     /* longer hold                                                       */
10194     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10195         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10196         nCmailMovesRegistered --;
10197
10198         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10199           {
10200               free(cmailCommentList[lastLoadGameNumber - 1]);
10201               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10202           }
10203     }
10204
10205     if (cmailOldMove == -1) {
10206         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10207         return FALSE;
10208     }
10209
10210     if (currentMove > cmailOldMove + 1) {
10211         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10212         return FALSE;
10213     }
10214
10215     if (currentMove < cmailOldMove) {
10216         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10217         return FALSE;
10218     }
10219
10220     if (forwardMostMove > currentMove) {
10221         /* Silently truncate extra moves */
10222         TruncateGame();
10223     }
10224
10225     if (   (currentMove == cmailOldMove + 1)
10226         || (   (currentMove == cmailOldMove)
10227             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10228                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10229         if (gameInfo.result != GameUnfinished) {
10230             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10231         }
10232
10233         if (commentList[currentMove] != NULL) {
10234             cmailCommentList[lastLoadGameNumber - 1]
10235               = StrSave(commentList[currentMove]);
10236         }
10237         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10238
10239         if (appData.debugMode)
10240           fprintf(debugFP, "Saving %s for game %d\n",
10241                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10242
10243         sprintf(string,
10244                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10245         
10246         f = fopen(string, "w");
10247         if (appData.oldSaveStyle) {
10248             SaveGameOldStyle(f); /* also closes the file */
10249             
10250             sprintf(string, "%s.pos.out", appData.cmailGameName);
10251             f = fopen(string, "w");
10252             SavePosition(f, 0, NULL); /* also closes the file */
10253         } else {
10254             fprintf(f, "{--------------\n");
10255             PrintPosition(f, currentMove);
10256             fprintf(f, "--------------}\n\n");
10257             
10258             SaveGame(f, 0, NULL); /* also closes the file*/
10259         }
10260         
10261         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10262         nCmailMovesRegistered ++;
10263     } else if (nCmailGames == 1) {
10264         DisplayError(_("You have not made a move yet"), 0);
10265         return FALSE;
10266     }
10267
10268     return TRUE;
10269 }
10270
10271 void
10272 MailMoveEvent()
10273 {
10274 #if !WIN32
10275     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10276     FILE *commandOutput;
10277     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10278     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10279     int nBuffers;
10280     int i;
10281     int archived;
10282     char *arcDir;
10283
10284     if (! cmailMsgLoaded) {
10285         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10286         return;
10287     }
10288
10289     if (nCmailGames == nCmailResults) {
10290         DisplayError(_("No unfinished games"), 0);
10291         return;
10292     }
10293
10294 #if CMAIL_PROHIBIT_REMAIL
10295     if (cmailMailedMove) {
10296         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);
10297         DisplayError(msg, 0);
10298         return;
10299     }
10300 #endif
10301
10302     if (! (cmailMailedMove || RegisterMove())) return;
10303     
10304     if (   cmailMailedMove
10305         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10306         sprintf(string, partCommandString,
10307                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10308         commandOutput = popen(string, "r");
10309
10310         if (commandOutput == NULL) {
10311             DisplayError(_("Failed to invoke cmail"), 0);
10312         } else {
10313             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10314                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10315             }
10316             if (nBuffers > 1) {
10317                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10318                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10319                 nBytes = MSG_SIZ - 1;
10320             } else {
10321                 (void) memcpy(msg, buffer, nBytes);
10322             }
10323             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10324
10325             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10326                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10327
10328                 archived = TRUE;
10329                 for (i = 0; i < nCmailGames; i ++) {
10330                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10331                         archived = FALSE;
10332                     }
10333                 }
10334                 if (   archived
10335                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10336                         != NULL)) {
10337                     sprintf(buffer, "%s/%s.%s.archive",
10338                             arcDir,
10339                             appData.cmailGameName,
10340                             gameInfo.date);
10341                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10342                     cmailMsgLoaded = FALSE;
10343                 }
10344             }
10345
10346             DisplayInformation(msg);
10347             pclose(commandOutput);
10348         }
10349     } else {
10350         if ((*cmailMsg) != '\0') {
10351             DisplayInformation(cmailMsg);
10352         }
10353     }
10354
10355     return;
10356 #endif /* !WIN32 */
10357 }
10358
10359 char *
10360 CmailMsg()
10361 {
10362 #if WIN32
10363     return NULL;
10364 #else
10365     int  prependComma = 0;
10366     char number[5];
10367     char string[MSG_SIZ];       /* Space for game-list */
10368     int  i;
10369     
10370     if (!cmailMsgLoaded) return "";
10371
10372     if (cmailMailedMove) {
10373         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10374     } else {
10375         /* Create a list of games left */
10376         sprintf(string, "[");
10377         for (i = 0; i < nCmailGames; i ++) {
10378             if (! (   cmailMoveRegistered[i]
10379                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10380                 if (prependComma) {
10381                     sprintf(number, ",%d", i + 1);
10382                 } else {
10383                     sprintf(number, "%d", i + 1);
10384                     prependComma = 1;
10385                 }
10386                 
10387                 strcat(string, number);
10388             }
10389         }
10390         strcat(string, "]");
10391
10392         if (nCmailMovesRegistered + nCmailResults == 0) {
10393             switch (nCmailGames) {
10394               case 1:
10395                 sprintf(cmailMsg,
10396                         _("Still need to make move for game\n"));
10397                 break;
10398                 
10399               case 2:
10400                 sprintf(cmailMsg,
10401                         _("Still need to make moves for both games\n"));
10402                 break;
10403                 
10404               default:
10405                 sprintf(cmailMsg,
10406                         _("Still need to make moves for all %d games\n"),
10407                         nCmailGames);
10408                 break;
10409             }
10410         } else {
10411             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10412               case 1:
10413                 sprintf(cmailMsg,
10414                         _("Still need to make a move for game %s\n"),
10415                         string);
10416                 break;
10417                 
10418               case 0:
10419                 if (nCmailResults == nCmailGames) {
10420                     sprintf(cmailMsg, _("No unfinished games\n"));
10421                 } else {
10422                     sprintf(cmailMsg, _("Ready to send mail\n"));
10423                 }
10424                 break;
10425                 
10426               default:
10427                 sprintf(cmailMsg,
10428                         _("Still need to make moves for games %s\n"),
10429                         string);
10430             }
10431         }
10432     }
10433     return cmailMsg;
10434 #endif /* WIN32 */
10435 }
10436
10437 void
10438 ResetGameEvent()
10439 {
10440     if (gameMode == Training)
10441       SetTrainingModeOff();
10442
10443     Reset(TRUE, TRUE);
10444     cmailMsgLoaded = FALSE;
10445     if (appData.icsActive) {
10446       SendToICS(ics_prefix);
10447       SendToICS("refresh\n");
10448     }
10449 }
10450
10451 void
10452 ExitEvent(status)
10453      int status;
10454 {
10455     exiting++;
10456     if (exiting > 2) {
10457       /* Give up on clean exit */
10458       exit(status);
10459     }
10460     if (exiting > 1) {
10461       /* Keep trying for clean exit */
10462       return;
10463     }
10464
10465     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10466
10467     if (telnetISR != NULL) {
10468       RemoveInputSource(telnetISR);
10469     }
10470     if (icsPR != NoProc) {
10471       DestroyChildProcess(icsPR, TRUE);
10472     }
10473
10474     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10475     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10476
10477     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10478     /* make sure this other one finishes before killing it!                  */
10479     if(endingGame) { int count = 0;
10480         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10481         while(endingGame && count++ < 10) DoSleep(1);
10482         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10483     }
10484
10485     /* Kill off chess programs */
10486     if (first.pr != NoProc) {
10487         ExitAnalyzeMode();
10488         
10489         DoSleep( appData.delayBeforeQuit );
10490         SendToProgram("quit\n", &first);
10491         DoSleep( appData.delayAfterQuit );
10492         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10493     }
10494     if (second.pr != NoProc) {
10495         DoSleep( appData.delayBeforeQuit );
10496         SendToProgram("quit\n", &second);
10497         DoSleep( appData.delayAfterQuit );
10498         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10499     }
10500     if (first.isr != NULL) {
10501         RemoveInputSource(first.isr);
10502     }
10503     if (second.isr != NULL) {
10504         RemoveInputSource(second.isr);
10505     }
10506
10507     ShutDownFrontEnd();
10508     exit(status);
10509 }
10510
10511 void
10512 PauseEvent()
10513 {
10514     if (appData.debugMode)
10515         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10516     if (pausing) {
10517         pausing = FALSE;
10518         ModeHighlight();
10519         if (gameMode == MachinePlaysWhite ||
10520             gameMode == MachinePlaysBlack) {
10521             StartClocks();
10522         } else {
10523             DisplayBothClocks();
10524         }
10525         if (gameMode == PlayFromGameFile) {
10526             if (appData.timeDelay >= 0) 
10527                 AutoPlayGameLoop();
10528         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10529             Reset(FALSE, TRUE);
10530             SendToICS(ics_prefix);
10531             SendToICS("refresh\n");
10532         } else if (currentMove < forwardMostMove) {
10533             ForwardInner(forwardMostMove);
10534         }
10535         pauseExamInvalid = FALSE;
10536     } else {
10537         switch (gameMode) {
10538           default:
10539             return;
10540           case IcsExamining:
10541             pauseExamForwardMostMove = forwardMostMove;
10542             pauseExamInvalid = FALSE;
10543             /* fall through */
10544           case IcsObserving:
10545           case IcsPlayingWhite:
10546           case IcsPlayingBlack:
10547             pausing = TRUE;
10548             ModeHighlight();
10549             return;
10550           case PlayFromGameFile:
10551             (void) StopLoadGameTimer();
10552             pausing = TRUE;
10553             ModeHighlight();
10554             break;
10555           case BeginningOfGame:
10556             if (appData.icsActive) return;
10557             /* else fall through */
10558           case MachinePlaysWhite:
10559           case MachinePlaysBlack:
10560           case TwoMachinesPlay:
10561             if (forwardMostMove == 0)
10562               return;           /* don't pause if no one has moved */
10563             if ((gameMode == MachinePlaysWhite &&
10564                  !WhiteOnMove(forwardMostMove)) ||
10565                 (gameMode == MachinePlaysBlack &&
10566                  WhiteOnMove(forwardMostMove))) {
10567                 StopClocks();
10568             }
10569             pausing = TRUE;
10570             ModeHighlight();
10571             break;
10572         }
10573     }
10574 }
10575
10576 void
10577 EditCommentEvent()
10578 {
10579     char title[MSG_SIZ];
10580
10581     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10582         strcpy(title, _("Edit comment"));
10583     } else {
10584         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10585                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10586                 parseList[currentMove - 1]);
10587     }
10588
10589     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10590 }
10591
10592
10593 void
10594 EditTagsEvent()
10595 {
10596     char *tags = PGNTags(&gameInfo);
10597     EditTagsPopUp(tags);
10598     free(tags);
10599 }
10600
10601 void
10602 AnalyzeModeEvent()
10603 {
10604     if (appData.noChessProgram || gameMode == AnalyzeMode)
10605       return;
10606
10607     if (gameMode != AnalyzeFile) {
10608         if (!appData.icsEngineAnalyze) {
10609                EditGameEvent();
10610                if (gameMode != EditGame) return;
10611         }
10612         ResurrectChessProgram();
10613         SendToProgram("analyze\n", &first);
10614         first.analyzing = TRUE;
10615         /*first.maybeThinking = TRUE;*/
10616         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10617         EngineOutputPopUp();
10618     }
10619     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10620     pausing = FALSE;
10621     ModeHighlight();
10622     SetGameInfo();
10623
10624     StartAnalysisClock();
10625     GetTimeMark(&lastNodeCountTime);
10626     lastNodeCount = 0;
10627 }
10628
10629 void
10630 AnalyzeFileEvent()
10631 {
10632     if (appData.noChessProgram || gameMode == AnalyzeFile)
10633       return;
10634
10635     if (gameMode != AnalyzeMode) {
10636         EditGameEvent();
10637         if (gameMode != EditGame) return;
10638         ResurrectChessProgram();
10639         SendToProgram("analyze\n", &first);
10640         first.analyzing = TRUE;
10641         /*first.maybeThinking = TRUE;*/
10642         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10643         EngineOutputPopUp();
10644     }
10645     gameMode = AnalyzeFile;
10646     pausing = FALSE;
10647     ModeHighlight();
10648     SetGameInfo();
10649
10650     StartAnalysisClock();
10651     GetTimeMark(&lastNodeCountTime);
10652     lastNodeCount = 0;
10653 }
10654
10655 void
10656 MachineWhiteEvent()
10657 {
10658     char buf[MSG_SIZ];
10659     char *bookHit = NULL;
10660
10661     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10662       return;
10663
10664
10665     if (gameMode == PlayFromGameFile || 
10666         gameMode == TwoMachinesPlay  || 
10667         gameMode == Training         || 
10668         gameMode == AnalyzeMode      || 
10669         gameMode == EndOfGame)
10670         EditGameEvent();
10671
10672     if (gameMode == EditPosition) 
10673         EditPositionDone(TRUE);
10674
10675     if (!WhiteOnMove(currentMove)) {
10676         DisplayError(_("It is not White's turn"), 0);
10677         return;
10678     }
10679   
10680     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10681       ExitAnalyzeMode();
10682
10683     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10684         gameMode == AnalyzeFile)
10685         TruncateGame();
10686
10687     ResurrectChessProgram();    /* in case it isn't running */
10688     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10689         gameMode = MachinePlaysWhite;
10690         ResetClocks();
10691     } else
10692     gameMode = MachinePlaysWhite;
10693     pausing = FALSE;
10694     ModeHighlight();
10695     SetGameInfo();
10696     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10697     DisplayTitle(buf);
10698     if (first.sendName) {
10699       sprintf(buf, "name %s\n", gameInfo.black);
10700       SendToProgram(buf, &first);
10701     }
10702     if (first.sendTime) {
10703       if (first.useColors) {
10704         SendToProgram("black\n", &first); /*gnu kludge*/
10705       }
10706       SendTimeRemaining(&first, TRUE);
10707     }
10708     if (first.useColors) {
10709       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10710     }
10711     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10712     SetMachineThinkingEnables();
10713     first.maybeThinking = TRUE;
10714     StartClocks();
10715     firstMove = FALSE;
10716
10717     if (appData.autoFlipView && !flipView) {
10718       flipView = !flipView;
10719       DrawPosition(FALSE, NULL);
10720       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10721     }
10722
10723     if(bookHit) { // [HGM] book: simulate book reply
10724         static char bookMove[MSG_SIZ]; // a bit generous?
10725
10726         programStats.nodes = programStats.depth = programStats.time = 
10727         programStats.score = programStats.got_only_move = 0;
10728         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10729
10730         strcpy(bookMove, "move ");
10731         strcat(bookMove, bookHit);
10732         HandleMachineMove(bookMove, &first);
10733     }
10734 }
10735
10736 void
10737 MachineBlackEvent()
10738 {
10739     char buf[MSG_SIZ];
10740    char *bookHit = NULL;
10741
10742     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10743         return;
10744
10745
10746     if (gameMode == PlayFromGameFile || 
10747         gameMode == TwoMachinesPlay  || 
10748         gameMode == Training         || 
10749         gameMode == AnalyzeMode      || 
10750         gameMode == EndOfGame)
10751         EditGameEvent();
10752
10753     if (gameMode == EditPosition) 
10754         EditPositionDone(TRUE);
10755
10756     if (WhiteOnMove(currentMove)) {
10757         DisplayError(_("It is not Black's turn"), 0);
10758         return;
10759     }
10760     
10761     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10762       ExitAnalyzeMode();
10763
10764     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10765         gameMode == AnalyzeFile)
10766         TruncateGame();
10767
10768     ResurrectChessProgram();    /* in case it isn't running */
10769     gameMode = MachinePlaysBlack;
10770     pausing = FALSE;
10771     ModeHighlight();
10772     SetGameInfo();
10773     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10774     DisplayTitle(buf);
10775     if (first.sendName) {
10776       sprintf(buf, "name %s\n", gameInfo.white);
10777       SendToProgram(buf, &first);
10778     }
10779     if (first.sendTime) {
10780       if (first.useColors) {
10781         SendToProgram("white\n", &first); /*gnu kludge*/
10782       }
10783       SendTimeRemaining(&first, FALSE);
10784     }
10785     if (first.useColors) {
10786       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10787     }
10788     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10789     SetMachineThinkingEnables();
10790     first.maybeThinking = TRUE;
10791     StartClocks();
10792
10793     if (appData.autoFlipView && flipView) {
10794       flipView = !flipView;
10795       DrawPosition(FALSE, NULL);
10796       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10797     }
10798     if(bookHit) { // [HGM] book: simulate book reply
10799         static char bookMove[MSG_SIZ]; // a bit generous?
10800
10801         programStats.nodes = programStats.depth = programStats.time = 
10802         programStats.score = programStats.got_only_move = 0;
10803         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10804
10805         strcpy(bookMove, "move ");
10806         strcat(bookMove, bookHit);
10807         HandleMachineMove(bookMove, &first);
10808     }
10809 }
10810
10811
10812 void
10813 DisplayTwoMachinesTitle()
10814 {
10815     char buf[MSG_SIZ];
10816     if (appData.matchGames > 0) {
10817         if (first.twoMachinesColor[0] == 'w') {
10818             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10819                     gameInfo.white, gameInfo.black,
10820                     first.matchWins, second.matchWins,
10821                     matchGame - 1 - (first.matchWins + second.matchWins));
10822         } else {
10823             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10824                     gameInfo.white, gameInfo.black,
10825                     second.matchWins, first.matchWins,
10826                     matchGame - 1 - (first.matchWins + second.matchWins));
10827         }
10828     } else {
10829         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10830     }
10831     DisplayTitle(buf);
10832 }
10833
10834 void
10835 TwoMachinesEvent P((void))
10836 {
10837     int i;
10838     char buf[MSG_SIZ];
10839     ChessProgramState *onmove;
10840     char *bookHit = NULL;
10841     
10842     if (appData.noChessProgram) return;
10843
10844     switch (gameMode) {
10845       case TwoMachinesPlay:
10846         return;
10847       case MachinePlaysWhite:
10848       case MachinePlaysBlack:
10849         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10850             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10851             return;
10852         }
10853         /* fall through */
10854       case BeginningOfGame:
10855       case PlayFromGameFile:
10856       case EndOfGame:
10857         EditGameEvent();
10858         if (gameMode != EditGame) return;
10859         break;
10860       case EditPosition:
10861         EditPositionDone(TRUE);
10862         break;
10863       case AnalyzeMode:
10864       case AnalyzeFile:
10865         ExitAnalyzeMode();
10866         break;
10867       case EditGame:
10868       default:
10869         break;
10870     }
10871
10872 //    forwardMostMove = currentMove;
10873     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10874     ResurrectChessProgram();    /* in case first program isn't running */
10875
10876     if (second.pr == NULL) {
10877         StartChessProgram(&second);
10878         if (second.protocolVersion == 1) {
10879           TwoMachinesEventIfReady();
10880         } else {
10881           /* kludge: allow timeout for initial "feature" command */
10882           FreezeUI();
10883           DisplayMessage("", _("Starting second chess program"));
10884           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10885         }
10886         return;
10887     }
10888     DisplayMessage("", "");
10889     InitChessProgram(&second, FALSE);
10890     SendToProgram("force\n", &second);
10891     if (startedFromSetupPosition) {
10892         SendBoard(&second, backwardMostMove);
10893     if (appData.debugMode) {
10894         fprintf(debugFP, "Two Machines\n");
10895     }
10896     }
10897     for (i = backwardMostMove; i < forwardMostMove; i++) {
10898         SendMoveToProgram(i, &second);
10899     }
10900
10901     gameMode = TwoMachinesPlay;
10902     pausing = FALSE;
10903     ModeHighlight();
10904     SetGameInfo();
10905     DisplayTwoMachinesTitle();
10906     firstMove = TRUE;
10907     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10908         onmove = &first;
10909     } else {
10910         onmove = &second;
10911     }
10912
10913     SendToProgram(first.computerString, &first);
10914     if (first.sendName) {
10915       sprintf(buf, "name %s\n", second.tidy);
10916       SendToProgram(buf, &first);
10917     }
10918     SendToProgram(second.computerString, &second);
10919     if (second.sendName) {
10920       sprintf(buf, "name %s\n", first.tidy);
10921       SendToProgram(buf, &second);
10922     }
10923
10924     ResetClocks();
10925     if (!first.sendTime || !second.sendTime) {
10926         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10927         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10928     }
10929     if (onmove->sendTime) {
10930       if (onmove->useColors) {
10931         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10932       }
10933       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10934     }
10935     if (onmove->useColors) {
10936       SendToProgram(onmove->twoMachinesColor, onmove);
10937     }
10938     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10939 //    SendToProgram("go\n", onmove);
10940     onmove->maybeThinking = TRUE;
10941     SetMachineThinkingEnables();
10942
10943     StartClocks();
10944
10945     if(bookHit) { // [HGM] book: simulate book reply
10946         static char bookMove[MSG_SIZ]; // a bit generous?
10947
10948         programStats.nodes = programStats.depth = programStats.time = 
10949         programStats.score = programStats.got_only_move = 0;
10950         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10951
10952         strcpy(bookMove, "move ");
10953         strcat(bookMove, bookHit);
10954         savedMessage = bookMove; // args for deferred call
10955         savedState = onmove;
10956         ScheduleDelayedEvent(DeferredBookMove, 1);
10957     }
10958 }
10959
10960 void
10961 TrainingEvent()
10962 {
10963     if (gameMode == Training) {
10964       SetTrainingModeOff();
10965       gameMode = PlayFromGameFile;
10966       DisplayMessage("", _("Training mode off"));
10967     } else {
10968       gameMode = Training;
10969       animateTraining = appData.animate;
10970
10971       /* make sure we are not already at the end of the game */
10972       if (currentMove < forwardMostMove) {
10973         SetTrainingModeOn();
10974         DisplayMessage("", _("Training mode on"));
10975       } else {
10976         gameMode = PlayFromGameFile;
10977         DisplayError(_("Already at end of game"), 0);
10978       }
10979     }
10980     ModeHighlight();
10981 }
10982
10983 void
10984 IcsClientEvent()
10985 {
10986     if (!appData.icsActive) return;
10987     switch (gameMode) {
10988       case IcsPlayingWhite:
10989       case IcsPlayingBlack:
10990       case IcsObserving:
10991       case IcsIdle:
10992       case BeginningOfGame:
10993       case IcsExamining:
10994         return;
10995
10996       case EditGame:
10997         break;
10998
10999       case EditPosition:
11000         EditPositionDone(TRUE);
11001         break;
11002
11003       case AnalyzeMode:
11004       case AnalyzeFile:
11005         ExitAnalyzeMode();
11006         break;
11007         
11008       default:
11009         EditGameEvent();
11010         break;
11011     }
11012
11013     gameMode = IcsIdle;
11014     ModeHighlight();
11015     return;
11016 }
11017
11018
11019 void
11020 EditGameEvent()
11021 {
11022     int i;
11023
11024     switch (gameMode) {
11025       case Training:
11026         SetTrainingModeOff();
11027         break;
11028       case MachinePlaysWhite:
11029       case MachinePlaysBlack:
11030       case BeginningOfGame:
11031         SendToProgram("force\n", &first);
11032         SetUserThinkingEnables();
11033         break;
11034       case PlayFromGameFile:
11035         (void) StopLoadGameTimer();
11036         if (gameFileFP != NULL) {
11037             gameFileFP = NULL;
11038         }
11039         break;
11040       case EditPosition:
11041         EditPositionDone(TRUE);
11042         break;
11043       case AnalyzeMode:
11044       case AnalyzeFile:
11045         ExitAnalyzeMode();
11046         SendToProgram("force\n", &first);
11047         break;
11048       case TwoMachinesPlay:
11049         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11050         ResurrectChessProgram();
11051         SetUserThinkingEnables();
11052         break;
11053       case EndOfGame:
11054         ResurrectChessProgram();
11055         break;
11056       case IcsPlayingBlack:
11057       case IcsPlayingWhite:
11058         DisplayError(_("Warning: You are still playing a game"), 0);
11059         break;
11060       case IcsObserving:
11061         DisplayError(_("Warning: You are still observing a game"), 0);
11062         break;
11063       case IcsExamining:
11064         DisplayError(_("Warning: You are still examining a game"), 0);
11065         break;
11066       case IcsIdle:
11067         break;
11068       case EditGame:
11069       default:
11070         return;
11071     }
11072     
11073     pausing = FALSE;
11074     StopClocks();
11075     first.offeredDraw = second.offeredDraw = 0;
11076
11077     if (gameMode == PlayFromGameFile) {
11078         whiteTimeRemaining = timeRemaining[0][currentMove];
11079         blackTimeRemaining = timeRemaining[1][currentMove];
11080         DisplayTitle("");
11081     }
11082
11083     if (gameMode == MachinePlaysWhite ||
11084         gameMode == MachinePlaysBlack ||
11085         gameMode == TwoMachinesPlay ||
11086         gameMode == EndOfGame) {
11087         i = forwardMostMove;
11088         while (i > currentMove) {
11089             SendToProgram("undo\n", &first);
11090             i--;
11091         }
11092         whiteTimeRemaining = timeRemaining[0][currentMove];
11093         blackTimeRemaining = timeRemaining[1][currentMove];
11094         DisplayBothClocks();
11095         if (whiteFlag || blackFlag) {
11096             whiteFlag = blackFlag = 0;
11097         }
11098         DisplayTitle("");
11099     }           
11100     
11101     gameMode = EditGame;
11102     ModeHighlight();
11103     SetGameInfo();
11104 }
11105
11106
11107 void
11108 EditPositionEvent()
11109 {
11110     if (gameMode == EditPosition) {
11111         EditGameEvent();
11112         return;
11113     }
11114     
11115     EditGameEvent();
11116     if (gameMode != EditGame) return;
11117     
11118     gameMode = EditPosition;
11119     ModeHighlight();
11120     SetGameInfo();
11121     if (currentMove > 0)
11122       CopyBoard(boards[0], boards[currentMove]);
11123     
11124     blackPlaysFirst = !WhiteOnMove(currentMove);
11125     ResetClocks();
11126     currentMove = forwardMostMove = backwardMostMove = 0;
11127     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11128     DisplayMove(-1);
11129 }
11130
11131 void
11132 ExitAnalyzeMode()
11133 {
11134     /* [DM] icsEngineAnalyze - possible call from other functions */
11135     if (appData.icsEngineAnalyze) {
11136         appData.icsEngineAnalyze = FALSE;
11137
11138         DisplayMessage("",_("Close ICS engine analyze..."));
11139     }
11140     if (first.analysisSupport && first.analyzing) {
11141       SendToProgram("exit\n", &first);
11142       first.analyzing = FALSE;
11143     }
11144     thinkOutput[0] = NULLCHAR;
11145 }
11146
11147 void
11148 EditPositionDone(Boolean fakeRights)
11149 {
11150     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11151
11152     startedFromSetupPosition = TRUE;
11153     InitChessProgram(&first, FALSE);
11154     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11155       boards[0][EP_STATUS] = EP_NONE;
11156       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11157     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11158         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11159         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11160       } else boards[0][CASTLING][2] = NoRights;
11161     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11162         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11163         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11164       } else boards[0][CASTLING][5] = NoRights;
11165     }
11166     SendToProgram("force\n", &first);
11167     if (blackPlaysFirst) {
11168         strcpy(moveList[0], "");
11169         strcpy(parseList[0], "");
11170         currentMove = forwardMostMove = backwardMostMove = 1;
11171         CopyBoard(boards[1], boards[0]);
11172     } else {
11173         currentMove = forwardMostMove = backwardMostMove = 0;
11174     }
11175     SendBoard(&first, forwardMostMove);
11176     if (appData.debugMode) {
11177         fprintf(debugFP, "EditPosDone\n");
11178     }
11179     DisplayTitle("");
11180     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11181     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11182     gameMode = EditGame;
11183     ModeHighlight();
11184     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11185     ClearHighlights(); /* [AS] */
11186 }
11187
11188 /* Pause for `ms' milliseconds */
11189 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11190 void
11191 TimeDelay(ms)
11192      long ms;
11193 {
11194     TimeMark m1, m2;
11195
11196     GetTimeMark(&m1);
11197     do {
11198         GetTimeMark(&m2);
11199     } while (SubtractTimeMarks(&m2, &m1) < ms);
11200 }
11201
11202 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11203 void
11204 SendMultiLineToICS(buf)
11205      char *buf;
11206 {
11207     char temp[MSG_SIZ+1], *p;
11208     int len;
11209
11210     len = strlen(buf);
11211     if (len > MSG_SIZ)
11212       len = MSG_SIZ;
11213   
11214     strncpy(temp, buf, len);
11215     temp[len] = 0;
11216
11217     p = temp;
11218     while (*p) {
11219         if (*p == '\n' || *p == '\r')
11220           *p = ' ';
11221         ++p;
11222     }
11223
11224     strcat(temp, "\n");
11225     SendToICS(temp);
11226     SendToPlayer(temp, strlen(temp));
11227 }
11228
11229 void
11230 SetWhiteToPlayEvent()
11231 {
11232     if (gameMode == EditPosition) {
11233         blackPlaysFirst = FALSE;
11234         DisplayBothClocks();    /* works because currentMove is 0 */
11235     } else if (gameMode == IcsExamining) {
11236         SendToICS(ics_prefix);
11237         SendToICS("tomove white\n");
11238     }
11239 }
11240
11241 void
11242 SetBlackToPlayEvent()
11243 {
11244     if (gameMode == EditPosition) {
11245         blackPlaysFirst = TRUE;
11246         currentMove = 1;        /* kludge */
11247         DisplayBothClocks();
11248         currentMove = 0;
11249     } else if (gameMode == IcsExamining) {
11250         SendToICS(ics_prefix);
11251         SendToICS("tomove black\n");
11252     }
11253 }
11254
11255 void
11256 EditPositionMenuEvent(selection, x, y)
11257      ChessSquare selection;
11258      int x, y;
11259 {
11260     char buf[MSG_SIZ];
11261     ChessSquare piece = boards[0][y][x];
11262
11263     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11264
11265     switch (selection) {
11266       case ClearBoard:
11267         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11268             SendToICS(ics_prefix);
11269             SendToICS("bsetup clear\n");
11270         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11271             SendToICS(ics_prefix);
11272             SendToICS("clearboard\n");
11273         } else {
11274             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11275                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11276                 for (y = 0; y < BOARD_HEIGHT; y++) {
11277                     if (gameMode == IcsExamining) {
11278                         if (boards[currentMove][y][x] != EmptySquare) {
11279                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11280                                     AAA + x, ONE + y);
11281                             SendToICS(buf);
11282                         }
11283                     } else {
11284                         boards[0][y][x] = p;
11285                     }
11286                 }
11287             }
11288         }
11289         if (gameMode == EditPosition) {
11290             DrawPosition(FALSE, boards[0]);
11291         }
11292         break;
11293
11294       case WhitePlay:
11295         SetWhiteToPlayEvent();
11296         break;
11297
11298       case BlackPlay:
11299         SetBlackToPlayEvent();
11300         break;
11301
11302       case EmptySquare:
11303         if (gameMode == IcsExamining) {
11304             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11305             SendToICS(buf);
11306         } else {
11307             boards[0][y][x] = EmptySquare;
11308             DrawPosition(FALSE, boards[0]);
11309         }
11310         break;
11311
11312       case PromotePiece:
11313         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11314            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11315             selection = (ChessSquare) (PROMOTED piece);
11316         } else if(piece == EmptySquare) selection = WhiteSilver;
11317         else selection = (ChessSquare)((int)piece - 1);
11318         goto defaultlabel;
11319
11320       case DemotePiece:
11321         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11322            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11323             selection = (ChessSquare) (DEMOTED piece);
11324         } else if(piece == EmptySquare) selection = BlackSilver;
11325         else selection = (ChessSquare)((int)piece + 1);       
11326         goto defaultlabel;
11327
11328       case WhiteQueen:
11329       case BlackQueen:
11330         if(gameInfo.variant == VariantShatranj ||
11331            gameInfo.variant == VariantXiangqi  ||
11332            gameInfo.variant == VariantCourier    )
11333             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11334         goto defaultlabel;
11335
11336       case WhiteKing:
11337       case BlackKing:
11338         if(gameInfo.variant == VariantXiangqi)
11339             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11340         if(gameInfo.variant == VariantKnightmate)
11341             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11342       default:
11343         defaultlabel:
11344         if (gameMode == IcsExamining) {
11345             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11346                     PieceToChar(selection), AAA + x, ONE + y);
11347             SendToICS(buf);
11348         } else {
11349             boards[0][y][x] = selection;
11350             DrawPosition(FALSE, boards[0]);
11351         }
11352         break;
11353     }
11354 }
11355
11356
11357 void
11358 DropMenuEvent(selection, x, y)
11359      ChessSquare selection;
11360      int x, y;
11361 {
11362     ChessMove moveType;
11363
11364     switch (gameMode) {
11365       case IcsPlayingWhite:
11366       case MachinePlaysBlack:
11367         if (!WhiteOnMove(currentMove)) {
11368             DisplayMoveError(_("It is Black's turn"));
11369             return;
11370         }
11371         moveType = WhiteDrop;
11372         break;
11373       case IcsPlayingBlack:
11374       case MachinePlaysWhite:
11375         if (WhiteOnMove(currentMove)) {
11376             DisplayMoveError(_("It is White's turn"));
11377             return;
11378         }
11379         moveType = BlackDrop;
11380         break;
11381       case EditGame:
11382         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11383         break;
11384       default:
11385         return;
11386     }
11387
11388     if (moveType == BlackDrop && selection < BlackPawn) {
11389       selection = (ChessSquare) ((int) selection
11390                                  + (int) BlackPawn - (int) WhitePawn);
11391     }
11392     if (boards[currentMove][y][x] != EmptySquare) {
11393         DisplayMoveError(_("That square is occupied"));
11394         return;
11395     }
11396
11397     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11398 }
11399
11400 void
11401 AcceptEvent()
11402 {
11403     /* Accept a pending offer of any kind from opponent */
11404     
11405     if (appData.icsActive) {
11406         SendToICS(ics_prefix);
11407         SendToICS("accept\n");
11408     } else if (cmailMsgLoaded) {
11409         if (currentMove == cmailOldMove &&
11410             commentList[cmailOldMove] != NULL &&
11411             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11412                    "Black offers a draw" : "White offers a draw")) {
11413             TruncateGame();
11414             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11415             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11416         } else {
11417             DisplayError(_("There is no pending offer on this move"), 0);
11418             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11419         }
11420     } else {
11421         /* Not used for offers from chess program */
11422     }
11423 }
11424
11425 void
11426 DeclineEvent()
11427 {
11428     /* Decline a pending offer of any kind from opponent */
11429     
11430     if (appData.icsActive) {
11431         SendToICS(ics_prefix);
11432         SendToICS("decline\n");
11433     } else if (cmailMsgLoaded) {
11434         if (currentMove == cmailOldMove &&
11435             commentList[cmailOldMove] != NULL &&
11436             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11437                    "Black offers a draw" : "White offers a draw")) {
11438 #ifdef NOTDEF
11439             AppendComment(cmailOldMove, "Draw declined", TRUE);
11440             DisplayComment(cmailOldMove - 1, "Draw declined");
11441 #endif /*NOTDEF*/
11442         } else {
11443             DisplayError(_("There is no pending offer on this move"), 0);
11444         }
11445     } else {
11446         /* Not used for offers from chess program */
11447     }
11448 }
11449
11450 void
11451 RematchEvent()
11452 {
11453     /* Issue ICS rematch command */
11454     if (appData.icsActive) {
11455         SendToICS(ics_prefix);
11456         SendToICS("rematch\n");
11457     }
11458 }
11459
11460 void
11461 CallFlagEvent()
11462 {
11463     /* Call your opponent's flag (claim a win on time) */
11464     if (appData.icsActive) {
11465         SendToICS(ics_prefix);
11466         SendToICS("flag\n");
11467     } else {
11468         switch (gameMode) {
11469           default:
11470             return;
11471           case MachinePlaysWhite:
11472             if (whiteFlag) {
11473                 if (blackFlag)
11474                   GameEnds(GameIsDrawn, "Both players ran out of time",
11475                            GE_PLAYER);
11476                 else
11477                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11478             } else {
11479                 DisplayError(_("Your opponent is not out of time"), 0);
11480             }
11481             break;
11482           case MachinePlaysBlack:
11483             if (blackFlag) {
11484                 if (whiteFlag)
11485                   GameEnds(GameIsDrawn, "Both players ran out of time",
11486                            GE_PLAYER);
11487                 else
11488                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11489             } else {
11490                 DisplayError(_("Your opponent is not out of time"), 0);
11491             }
11492             break;
11493         }
11494     }
11495 }
11496
11497 void
11498 DrawEvent()
11499 {
11500     /* Offer draw or accept pending draw offer from opponent */
11501     
11502     if (appData.icsActive) {
11503         /* Note: tournament rules require draw offers to be
11504            made after you make your move but before you punch
11505            your clock.  Currently ICS doesn't let you do that;
11506            instead, you immediately punch your clock after making
11507            a move, but you can offer a draw at any time. */
11508         
11509         SendToICS(ics_prefix);
11510         SendToICS("draw\n");
11511     } else if (cmailMsgLoaded) {
11512         if (currentMove == cmailOldMove &&
11513             commentList[cmailOldMove] != NULL &&
11514             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11515                    "Black offers a draw" : "White offers a draw")) {
11516             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11517             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11518         } else if (currentMove == cmailOldMove + 1) {
11519             char *offer = WhiteOnMove(cmailOldMove) ?
11520               "White offers a draw" : "Black offers a draw";
11521             AppendComment(currentMove, offer, TRUE);
11522             DisplayComment(currentMove - 1, offer);
11523             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11524         } else {
11525             DisplayError(_("You must make your move before offering a draw"), 0);
11526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11527         }
11528     } else if (first.offeredDraw) {
11529         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11530     } else {
11531         if (first.sendDrawOffers) {
11532             SendToProgram("draw\n", &first);
11533             userOfferedDraw = TRUE;
11534         }
11535     }
11536 }
11537
11538 void
11539 AdjournEvent()
11540 {
11541     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11542     
11543     if (appData.icsActive) {
11544         SendToICS(ics_prefix);
11545         SendToICS("adjourn\n");
11546     } else {
11547         /* Currently GNU Chess doesn't offer or accept Adjourns */
11548     }
11549 }
11550
11551
11552 void
11553 AbortEvent()
11554 {
11555     /* Offer Abort or accept pending Abort offer from opponent */
11556     
11557     if (appData.icsActive) {
11558         SendToICS(ics_prefix);
11559         SendToICS("abort\n");
11560     } else {
11561         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11562     }
11563 }
11564
11565 void
11566 ResignEvent()
11567 {
11568     /* Resign.  You can do this even if it's not your turn. */
11569     
11570     if (appData.icsActive) {
11571         SendToICS(ics_prefix);
11572         SendToICS("resign\n");
11573     } else {
11574         switch (gameMode) {
11575           case MachinePlaysWhite:
11576             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11577             break;
11578           case MachinePlaysBlack:
11579             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11580             break;
11581           case EditGame:
11582             if (cmailMsgLoaded) {
11583                 TruncateGame();
11584                 if (WhiteOnMove(cmailOldMove)) {
11585                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11586                 } else {
11587                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11588                 }
11589                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11590             }
11591             break;
11592           default:
11593             break;
11594         }
11595     }
11596 }
11597
11598
11599 void
11600 StopObservingEvent()
11601 {
11602     /* Stop observing current games */
11603     SendToICS(ics_prefix);
11604     SendToICS("unobserve\n");
11605 }
11606
11607 void
11608 StopExaminingEvent()
11609 {
11610     /* Stop observing current game */
11611     SendToICS(ics_prefix);
11612     SendToICS("unexamine\n");
11613 }
11614
11615 void
11616 ForwardInner(target)
11617      int target;
11618 {
11619     int limit;
11620
11621     if (appData.debugMode)
11622         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11623                 target, currentMove, forwardMostMove);
11624
11625     if (gameMode == EditPosition)
11626       return;
11627
11628     if (gameMode == PlayFromGameFile && !pausing)
11629       PauseEvent();
11630     
11631     if (gameMode == IcsExamining && pausing)
11632       limit = pauseExamForwardMostMove;
11633     else
11634       limit = forwardMostMove;
11635     
11636     if (target > limit) target = limit;
11637
11638     if (target > 0 && moveList[target - 1][0]) {
11639         int fromX, fromY, toX, toY;
11640         toX = moveList[target - 1][2] - AAA;
11641         toY = moveList[target - 1][3] - ONE;
11642         if (moveList[target - 1][1] == '@') {
11643             if (appData.highlightLastMove) {
11644                 SetHighlights(-1, -1, toX, toY);
11645             }
11646         } else {
11647             fromX = moveList[target - 1][0] - AAA;
11648             fromY = moveList[target - 1][1] - ONE;
11649             if (target == currentMove + 1) {
11650                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11651             }
11652             if (appData.highlightLastMove) {
11653                 SetHighlights(fromX, fromY, toX, toY);
11654             }
11655         }
11656     }
11657     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11658         gameMode == Training || gameMode == PlayFromGameFile || 
11659         gameMode == AnalyzeFile) {
11660         while (currentMove < target) {
11661             SendMoveToProgram(currentMove++, &first);
11662         }
11663     } else {
11664         currentMove = target;
11665     }
11666     
11667     if (gameMode == EditGame || gameMode == EndOfGame) {
11668         whiteTimeRemaining = timeRemaining[0][currentMove];
11669         blackTimeRemaining = timeRemaining[1][currentMove];
11670     }
11671     DisplayBothClocks();
11672     DisplayMove(currentMove - 1);
11673     DrawPosition(FALSE, boards[currentMove]);
11674     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11675     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11676         DisplayComment(currentMove - 1, commentList[currentMove]);
11677     }
11678 }
11679
11680
11681 void
11682 ForwardEvent()
11683 {
11684     if (gameMode == IcsExamining && !pausing) {
11685         SendToICS(ics_prefix);
11686         SendToICS("forward\n");
11687     } else {
11688         ForwardInner(currentMove + 1);
11689     }
11690 }
11691
11692 void
11693 ToEndEvent()
11694 {
11695     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11696         /* to optimze, we temporarily turn off analysis mode while we feed
11697          * the remaining moves to the engine. Otherwise we get analysis output
11698          * after each move.
11699          */ 
11700         if (first.analysisSupport) {
11701           SendToProgram("exit\nforce\n", &first);
11702           first.analyzing = FALSE;
11703         }
11704     }
11705         
11706     if (gameMode == IcsExamining && !pausing) {
11707         SendToICS(ics_prefix);
11708         SendToICS("forward 999999\n");
11709     } else {
11710         ForwardInner(forwardMostMove);
11711     }
11712
11713     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11714         /* we have fed all the moves, so reactivate analysis mode */
11715         SendToProgram("analyze\n", &first);
11716         first.analyzing = TRUE;
11717         /*first.maybeThinking = TRUE;*/
11718         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11719     }
11720 }
11721
11722 void
11723 BackwardInner(target)
11724      int target;
11725 {
11726     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11727
11728     if (appData.debugMode)
11729         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11730                 target, currentMove, forwardMostMove);
11731
11732     if (gameMode == EditPosition) return;
11733     if (currentMove <= backwardMostMove) {
11734         ClearHighlights();
11735         DrawPosition(full_redraw, boards[currentMove]);
11736         return;
11737     }
11738     if (gameMode == PlayFromGameFile && !pausing)
11739       PauseEvent();
11740     
11741     if (moveList[target][0]) {
11742         int fromX, fromY, toX, toY;
11743         toX = moveList[target][2] - AAA;
11744         toY = moveList[target][3] - ONE;
11745         if (moveList[target][1] == '@') {
11746             if (appData.highlightLastMove) {
11747                 SetHighlights(-1, -1, toX, toY);
11748             }
11749         } else {
11750             fromX = moveList[target][0] - AAA;
11751             fromY = moveList[target][1] - ONE;
11752             if (target == currentMove - 1) {
11753                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11754             }
11755             if (appData.highlightLastMove) {
11756                 SetHighlights(fromX, fromY, toX, toY);
11757             }
11758         }
11759     }
11760     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11761         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11762         while (currentMove > target) {
11763             SendToProgram("undo\n", &first);
11764             currentMove--;
11765         }
11766     } else {
11767         currentMove = target;
11768     }
11769     
11770     if (gameMode == EditGame || gameMode == EndOfGame) {
11771         whiteTimeRemaining = timeRemaining[0][currentMove];
11772         blackTimeRemaining = timeRemaining[1][currentMove];
11773     }
11774     DisplayBothClocks();
11775     DisplayMove(currentMove - 1);
11776     DrawPosition(full_redraw, boards[currentMove]);
11777     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11778     // [HGM] PV info: routine tests if comment empty
11779     DisplayComment(currentMove - 1, commentList[currentMove]);
11780 }
11781
11782 void
11783 BackwardEvent()
11784 {
11785     if (gameMode == IcsExamining && !pausing) {
11786         SendToICS(ics_prefix);
11787         SendToICS("backward\n");
11788     } else {
11789         BackwardInner(currentMove - 1);
11790     }
11791 }
11792
11793 void
11794 ToStartEvent()
11795 {
11796     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11797         /* to optimize, we temporarily turn off analysis mode while we undo
11798          * all the moves. Otherwise we get analysis output after each undo.
11799          */ 
11800         if (first.analysisSupport) {
11801           SendToProgram("exit\nforce\n", &first);
11802           first.analyzing = FALSE;
11803         }
11804     }
11805
11806     if (gameMode == IcsExamining && !pausing) {
11807         SendToICS(ics_prefix);
11808         SendToICS("backward 999999\n");
11809     } else {
11810         BackwardInner(backwardMostMove);
11811     }
11812
11813     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11814         /* we have fed all the moves, so reactivate analysis mode */
11815         SendToProgram("analyze\n", &first);
11816         first.analyzing = TRUE;
11817         /*first.maybeThinking = TRUE;*/
11818         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11819     }
11820 }
11821
11822 void
11823 ToNrEvent(int to)
11824 {
11825   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11826   if (to >= forwardMostMove) to = forwardMostMove;
11827   if (to <= backwardMostMove) to = backwardMostMove;
11828   if (to < currentMove) {
11829     BackwardInner(to);
11830   } else {
11831     ForwardInner(to);
11832   }
11833 }
11834
11835 void
11836 RevertEvent()
11837 {
11838     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11839         return;
11840     }
11841     if (gameMode != IcsExamining) {
11842         DisplayError(_("You are not examining a game"), 0);
11843         return;
11844     }
11845     if (pausing) {
11846         DisplayError(_("You can't revert while pausing"), 0);
11847         return;
11848     }
11849     SendToICS(ics_prefix);
11850     SendToICS("revert\n");
11851 }
11852
11853 void
11854 RetractMoveEvent()
11855 {
11856     switch (gameMode) {
11857       case MachinePlaysWhite:
11858       case MachinePlaysBlack:
11859         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11860             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11861             return;
11862         }
11863         if (forwardMostMove < 2) return;
11864         currentMove = forwardMostMove = forwardMostMove - 2;
11865         whiteTimeRemaining = timeRemaining[0][currentMove];
11866         blackTimeRemaining = timeRemaining[1][currentMove];
11867         DisplayBothClocks();
11868         DisplayMove(currentMove - 1);
11869         ClearHighlights();/*!! could figure this out*/
11870         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11871         SendToProgram("remove\n", &first);
11872         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11873         break;
11874
11875       case BeginningOfGame:
11876       default:
11877         break;
11878
11879       case IcsPlayingWhite:
11880       case IcsPlayingBlack:
11881         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11882             SendToICS(ics_prefix);
11883             SendToICS("takeback 2\n");
11884         } else {
11885             SendToICS(ics_prefix);
11886             SendToICS("takeback 1\n");
11887         }
11888         break;
11889     }
11890 }
11891
11892 void
11893 MoveNowEvent()
11894 {
11895     ChessProgramState *cps;
11896
11897     switch (gameMode) {
11898       case MachinePlaysWhite:
11899         if (!WhiteOnMove(forwardMostMove)) {
11900             DisplayError(_("It is your turn"), 0);
11901             return;
11902         }
11903         cps = &first;
11904         break;
11905       case MachinePlaysBlack:
11906         if (WhiteOnMove(forwardMostMove)) {
11907             DisplayError(_("It is your turn"), 0);
11908             return;
11909         }
11910         cps = &first;
11911         break;
11912       case TwoMachinesPlay:
11913         if (WhiteOnMove(forwardMostMove) ==
11914             (first.twoMachinesColor[0] == 'w')) {
11915             cps = &first;
11916         } else {
11917             cps = &second;
11918         }
11919         break;
11920       case BeginningOfGame:
11921       default:
11922         return;
11923     }
11924     SendToProgram("?\n", cps);
11925 }
11926
11927 void
11928 TruncateGameEvent()
11929 {
11930     EditGameEvent();
11931     if (gameMode != EditGame) return;
11932     TruncateGame();
11933 }
11934
11935 void
11936 TruncateGame()
11937 {
11938     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11939     if (forwardMostMove > currentMove) {
11940         if (gameInfo.resultDetails != NULL) {
11941             free(gameInfo.resultDetails);
11942             gameInfo.resultDetails = NULL;
11943             gameInfo.result = GameUnfinished;
11944         }
11945         forwardMostMove = currentMove;
11946         HistorySet(parseList, backwardMostMove, forwardMostMove,
11947                    currentMove-1);
11948     }
11949 }
11950
11951 void
11952 HintEvent()
11953 {
11954     if (appData.noChessProgram) return;
11955     switch (gameMode) {
11956       case MachinePlaysWhite:
11957         if (WhiteOnMove(forwardMostMove)) {
11958             DisplayError(_("Wait until your turn"), 0);
11959             return;
11960         }
11961         break;
11962       case BeginningOfGame:
11963       case MachinePlaysBlack:
11964         if (!WhiteOnMove(forwardMostMove)) {
11965             DisplayError(_("Wait until your turn"), 0);
11966             return;
11967         }
11968         break;
11969       default:
11970         DisplayError(_("No hint available"), 0);
11971         return;
11972     }
11973     SendToProgram("hint\n", &first);
11974     hintRequested = TRUE;
11975 }
11976
11977 void
11978 BookEvent()
11979 {
11980     if (appData.noChessProgram) return;
11981     switch (gameMode) {
11982       case MachinePlaysWhite:
11983         if (WhiteOnMove(forwardMostMove)) {
11984             DisplayError(_("Wait until your turn"), 0);
11985             return;
11986         }
11987         break;
11988       case BeginningOfGame:
11989       case MachinePlaysBlack:
11990         if (!WhiteOnMove(forwardMostMove)) {
11991             DisplayError(_("Wait until your turn"), 0);
11992             return;
11993         }
11994         break;
11995       case EditPosition:
11996         EditPositionDone(TRUE);
11997         break;
11998       case TwoMachinesPlay:
11999         return;
12000       default:
12001         break;
12002     }
12003     SendToProgram("bk\n", &first);
12004     bookOutput[0] = NULLCHAR;
12005     bookRequested = TRUE;
12006 }
12007
12008 void
12009 AboutGameEvent()
12010 {
12011     char *tags = PGNTags(&gameInfo);
12012     TagsPopUp(tags, CmailMsg());
12013     free(tags);
12014 }
12015
12016 /* end button procedures */
12017
12018 void
12019 PrintPosition(fp, move)
12020      FILE *fp;
12021      int move;
12022 {
12023     int i, j;
12024     
12025     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12026         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12027             char c = PieceToChar(boards[move][i][j]);
12028             fputc(c == 'x' ? '.' : c, fp);
12029             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12030         }
12031     }
12032     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12033       fprintf(fp, "white to play\n");
12034     else
12035       fprintf(fp, "black to play\n");
12036 }
12037
12038 void
12039 PrintOpponents(fp)
12040      FILE *fp;
12041 {
12042     if (gameInfo.white != NULL) {
12043         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12044     } else {
12045         fprintf(fp, "\n");
12046     }
12047 }
12048
12049 /* Find last component of program's own name, using some heuristics */
12050 void
12051 TidyProgramName(prog, host, buf)
12052      char *prog, *host, buf[MSG_SIZ];
12053 {
12054     char *p, *q;
12055     int local = (strcmp(host, "localhost") == 0);
12056     while (!local && (p = strchr(prog, ';')) != NULL) {
12057         p++;
12058         while (*p == ' ') p++;
12059         prog = p;
12060     }
12061     if (*prog == '"' || *prog == '\'') {
12062         q = strchr(prog + 1, *prog);
12063     } else {
12064         q = strchr(prog, ' ');
12065     }
12066     if (q == NULL) q = prog + strlen(prog);
12067     p = q;
12068     while (p >= prog && *p != '/' && *p != '\\') p--;
12069     p++;
12070     if(p == prog && *p == '"') p++;
12071     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12072     memcpy(buf, p, q - p);
12073     buf[q - p] = NULLCHAR;
12074     if (!local) {
12075         strcat(buf, "@");
12076         strcat(buf, host);
12077     }
12078 }
12079
12080 char *
12081 TimeControlTagValue()
12082 {
12083     char buf[MSG_SIZ];
12084     if (!appData.clockMode) {
12085         strcpy(buf, "-");
12086     } else if (movesPerSession > 0) {
12087         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12088     } else if (timeIncrement == 0) {
12089         sprintf(buf, "%ld", timeControl/1000);
12090     } else {
12091         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12092     }
12093     return StrSave(buf);
12094 }
12095
12096 void
12097 SetGameInfo()
12098 {
12099     /* This routine is used only for certain modes */
12100     VariantClass v = gameInfo.variant;
12101     ChessMove r = GameUnfinished;
12102     char *p = NULL;
12103
12104     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12105         r = gameInfo.result; 
12106         p = gameInfo.resultDetails; 
12107         gameInfo.resultDetails = NULL;
12108     }
12109     ClearGameInfo(&gameInfo);
12110     gameInfo.variant = v;
12111
12112     switch (gameMode) {
12113       case MachinePlaysWhite:
12114         gameInfo.event = StrSave( appData.pgnEventHeader );
12115         gameInfo.site = StrSave(HostName());
12116         gameInfo.date = PGNDate();
12117         gameInfo.round = StrSave("-");
12118         gameInfo.white = StrSave(first.tidy);
12119         gameInfo.black = StrSave(UserName());
12120         gameInfo.timeControl = TimeControlTagValue();
12121         break;
12122
12123       case MachinePlaysBlack:
12124         gameInfo.event = StrSave( appData.pgnEventHeader );
12125         gameInfo.site = StrSave(HostName());
12126         gameInfo.date = PGNDate();
12127         gameInfo.round = StrSave("-");
12128         gameInfo.white = StrSave(UserName());
12129         gameInfo.black = StrSave(first.tidy);
12130         gameInfo.timeControl = TimeControlTagValue();
12131         break;
12132
12133       case TwoMachinesPlay:
12134         gameInfo.event = StrSave( appData.pgnEventHeader );
12135         gameInfo.site = StrSave(HostName());
12136         gameInfo.date = PGNDate();
12137         if (matchGame > 0) {
12138             char buf[MSG_SIZ];
12139             sprintf(buf, "%d", matchGame);
12140             gameInfo.round = StrSave(buf);
12141         } else {
12142             gameInfo.round = StrSave("-");
12143         }
12144         if (first.twoMachinesColor[0] == 'w') {
12145             gameInfo.white = StrSave(first.tidy);
12146             gameInfo.black = StrSave(second.tidy);
12147         } else {
12148             gameInfo.white = StrSave(second.tidy);
12149             gameInfo.black = StrSave(first.tidy);
12150         }
12151         gameInfo.timeControl = TimeControlTagValue();
12152         break;
12153
12154       case EditGame:
12155         gameInfo.event = StrSave("Edited game");
12156         gameInfo.site = StrSave(HostName());
12157         gameInfo.date = PGNDate();
12158         gameInfo.round = StrSave("-");
12159         gameInfo.white = StrSave("-");
12160         gameInfo.black = StrSave("-");
12161         gameInfo.result = r;
12162         gameInfo.resultDetails = p;
12163         break;
12164
12165       case EditPosition:
12166         gameInfo.event = StrSave("Edited position");
12167         gameInfo.site = StrSave(HostName());
12168         gameInfo.date = PGNDate();
12169         gameInfo.round = StrSave("-");
12170         gameInfo.white = StrSave("-");
12171         gameInfo.black = StrSave("-");
12172         break;
12173
12174       case IcsPlayingWhite:
12175       case IcsPlayingBlack:
12176       case IcsObserving:
12177       case IcsExamining:
12178         break;
12179
12180       case PlayFromGameFile:
12181         gameInfo.event = StrSave("Game from non-PGN file");
12182         gameInfo.site = StrSave(HostName());
12183         gameInfo.date = PGNDate();
12184         gameInfo.round = StrSave("-");
12185         gameInfo.white = StrSave("?");
12186         gameInfo.black = StrSave("?");
12187         break;
12188
12189       default:
12190         break;
12191     }
12192 }
12193
12194 void
12195 ReplaceComment(index, text)
12196      int index;
12197      char *text;
12198 {
12199     int len;
12200
12201     while (*text == '\n') text++;
12202     len = strlen(text);
12203     while (len > 0 && text[len - 1] == '\n') len--;
12204
12205     if (commentList[index] != NULL)
12206       free(commentList[index]);
12207
12208     if (len == 0) {
12209         commentList[index] = NULL;
12210         return;
12211     }
12212   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12213       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12214       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12215     commentList[index] = (char *) malloc(len + 2);
12216     strncpy(commentList[index], text, len);
12217     commentList[index][len] = '\n';
12218     commentList[index][len + 1] = NULLCHAR;
12219   } else { 
12220     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12221     char *p;
12222     commentList[index] = (char *) malloc(len + 6);
12223     strcpy(commentList[index], "{\n");
12224     strncpy(commentList[index]+2, text, len);
12225     commentList[index][len+2] = NULLCHAR;
12226     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12227     strcat(commentList[index], "\n}\n");
12228   }
12229 }
12230
12231 void
12232 CrushCRs(text)
12233      char *text;
12234 {
12235   char *p = text;
12236   char *q = text;
12237   char ch;
12238
12239   do {
12240     ch = *p++;
12241     if (ch == '\r') continue;
12242     *q++ = ch;
12243   } while (ch != '\0');
12244 }
12245
12246 void
12247 AppendComment(index, text, addBraces)
12248      int index;
12249      char *text;
12250      Boolean addBraces; // [HGM] braces: tells if we should add {}
12251 {
12252     int oldlen, len;
12253     char *old;
12254
12255 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12256     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12257
12258     CrushCRs(text);
12259     while (*text == '\n') text++;
12260     len = strlen(text);
12261     while (len > 0 && text[len - 1] == '\n') len--;
12262
12263     if (len == 0) return;
12264
12265     if (commentList[index] != NULL) {
12266         old = commentList[index];
12267         oldlen = strlen(old);
12268         while(commentList[index][oldlen-1] ==  '\n')
12269           commentList[index][--oldlen] = NULLCHAR;
12270         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12271         strcpy(commentList[index], old);
12272         free(old);
12273         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12274         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12275           if(addBraces) addBraces = FALSE; else { text++; len--; }
12276           while (*text == '\n') { text++; len--; }
12277           commentList[index][--oldlen] = NULLCHAR;
12278       }
12279         if(addBraces) strcat(commentList[index], "\n{\n");
12280         else          strcat(commentList[index], "\n");
12281         strcat(commentList[index], text);
12282         if(addBraces) strcat(commentList[index], "\n}\n");
12283         else          strcat(commentList[index], "\n");
12284     } else {
12285         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12286         if(addBraces)
12287              strcpy(commentList[index], "{\n");
12288         else commentList[index][0] = NULLCHAR;
12289         strcat(commentList[index], text);
12290         strcat(commentList[index], "\n");
12291         if(addBraces) strcat(commentList[index], "}\n");
12292     }
12293 }
12294
12295 static char * FindStr( char * text, char * sub_text )
12296 {
12297     char * result = strstr( text, sub_text );
12298
12299     if( result != NULL ) {
12300         result += strlen( sub_text );
12301     }
12302
12303     return result;
12304 }
12305
12306 /* [AS] Try to extract PV info from PGN comment */
12307 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12308 char *GetInfoFromComment( int index, char * text )
12309 {
12310     char * sep = text;
12311
12312     if( text != NULL && index > 0 ) {
12313         int score = 0;
12314         int depth = 0;
12315         int time = -1, sec = 0, deci;
12316         char * s_eval = FindStr( text, "[%eval " );
12317         char * s_emt = FindStr( text, "[%emt " );
12318
12319         if( s_eval != NULL || s_emt != NULL ) {
12320             /* New style */
12321             char delim;
12322
12323             if( s_eval != NULL ) {
12324                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12325                     return text;
12326                 }
12327
12328                 if( delim != ']' ) {
12329                     return text;
12330                 }
12331             }
12332
12333             if( s_emt != NULL ) {
12334             }
12335                 return text;
12336         }
12337         else {
12338             /* We expect something like: [+|-]nnn.nn/dd */
12339             int score_lo = 0;
12340
12341             if(*text != '{') return text; // [HGM] braces: must be normal comment
12342
12343             sep = strchr( text, '/' );
12344             if( sep == NULL || sep < (text+4) ) {
12345                 return text;
12346             }
12347
12348             time = -1; sec = -1; deci = -1;
12349             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12350                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12351                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12352                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12353                 return text;
12354             }
12355
12356             if( score_lo < 0 || score_lo >= 100 ) {
12357                 return text;
12358             }
12359
12360             if(sec >= 0) time = 600*time + 10*sec; else
12361             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12362
12363             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12364
12365             /* [HGM] PV time: now locate end of PV info */
12366             while( *++sep >= '0' && *sep <= '9'); // strip depth
12367             if(time >= 0)
12368             while( *++sep >= '0' && *sep <= '9'); // strip time
12369             if(sec >= 0)
12370             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12371             if(deci >= 0)
12372             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12373             while(*sep == ' ') sep++;
12374         }
12375
12376         if( depth <= 0 ) {
12377             return text;
12378         }
12379
12380         if( time < 0 ) {
12381             time = -1;
12382         }
12383
12384         pvInfoList[index-1].depth = depth;
12385         pvInfoList[index-1].score = score;
12386         pvInfoList[index-1].time  = 10*time; // centi-sec
12387         if(*sep == '}') *sep = 0; else *--sep = '{';
12388     }
12389     return sep;
12390 }
12391
12392 void
12393 SendToProgram(message, cps)
12394      char *message;
12395      ChessProgramState *cps;
12396 {
12397     int count, outCount, error;
12398     char buf[MSG_SIZ];
12399
12400     if (cps->pr == NULL) return;
12401     Attention(cps);
12402     
12403     if (appData.debugMode) {
12404         TimeMark now;
12405         GetTimeMark(&now);
12406         fprintf(debugFP, "%ld >%-6s: %s", 
12407                 SubtractTimeMarks(&now, &programStartTime),
12408                 cps->which, message);
12409     }
12410     
12411     count = strlen(message);
12412     outCount = OutputToProcess(cps->pr, message, count, &error);
12413     if (outCount < count && !exiting 
12414                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12415         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12416         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12417             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12418                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12419                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12420             } else {
12421                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12422             }
12423             gameInfo.resultDetails = StrSave(buf);
12424         }
12425         DisplayFatalError(buf, error, 1);
12426     }
12427 }
12428
12429 void
12430 ReceiveFromProgram(isr, closure, message, count, error)
12431      InputSourceRef isr;
12432      VOIDSTAR closure;
12433      char *message;
12434      int count;
12435      int error;
12436 {
12437     char *end_str;
12438     char buf[MSG_SIZ];
12439     ChessProgramState *cps = (ChessProgramState *)closure;
12440
12441     if (isr != cps->isr) return; /* Killed intentionally */
12442     if (count <= 0) {
12443         if (count == 0) {
12444             sprintf(buf,
12445                     _("Error: %s chess program (%s) exited unexpectedly"),
12446                     cps->which, cps->program);
12447         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12448                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12449                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12450                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12451                 } else {
12452                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12453                 }
12454                 gameInfo.resultDetails = StrSave(buf);
12455             }
12456             RemoveInputSource(cps->isr);
12457             DisplayFatalError(buf, 0, 1);
12458         } else {
12459             sprintf(buf,
12460                     _("Error reading from %s chess program (%s)"),
12461                     cps->which, cps->program);
12462             RemoveInputSource(cps->isr);
12463
12464             /* [AS] Program is misbehaving badly... kill it */
12465             if( count == -2 ) {
12466                 DestroyChildProcess( cps->pr, 9 );
12467                 cps->pr = NoProc;
12468             }
12469
12470             DisplayFatalError(buf, error, 1);
12471         }
12472         return;
12473     }
12474     
12475     if ((end_str = strchr(message, '\r')) != NULL)
12476       *end_str = NULLCHAR;
12477     if ((end_str = strchr(message, '\n')) != NULL)
12478       *end_str = NULLCHAR;
12479     
12480     if (appData.debugMode) {
12481         TimeMark now; int print = 1;
12482         char *quote = ""; char c; int i;
12483
12484         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12485                 char start = message[0];
12486                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12487                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12488                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12489                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12490                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12491                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12492                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12493                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12494                         { quote = "# "; print = (appData.engineComments == 2); }
12495                 message[0] = start; // restore original message
12496         }
12497         if(print) {
12498                 GetTimeMark(&now);
12499                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12500                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12501                         quote,
12502                         message);
12503         }
12504     }
12505
12506     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12507     if (appData.icsEngineAnalyze) {
12508         if (strstr(message, "whisper") != NULL ||
12509              strstr(message, "kibitz") != NULL || 
12510             strstr(message, "tellics") != NULL) return;
12511     }
12512
12513     HandleMachineMove(message, cps);
12514 }
12515
12516
12517 void
12518 SendTimeControl(cps, mps, tc, inc, sd, st)
12519      ChessProgramState *cps;
12520      int mps, inc, sd, st;
12521      long tc;
12522 {
12523     char buf[MSG_SIZ];
12524     int seconds;
12525
12526     if( timeControl_2 > 0 ) {
12527         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12528             tc = timeControl_2;
12529         }
12530     }
12531     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12532     inc /= cps->timeOdds;
12533     st  /= cps->timeOdds;
12534
12535     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12536
12537     if (st > 0) {
12538       /* Set exact time per move, normally using st command */
12539       if (cps->stKludge) {
12540         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12541         seconds = st % 60;
12542         if (seconds == 0) {
12543           sprintf(buf, "level 1 %d\n", st/60);
12544         } else {
12545           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12546         }
12547       } else {
12548         sprintf(buf, "st %d\n", st);
12549       }
12550     } else {
12551       /* Set conventional or incremental time control, using level command */
12552       if (seconds == 0) {
12553         /* Note old gnuchess bug -- minutes:seconds used to not work.
12554            Fixed in later versions, but still avoid :seconds
12555            when seconds is 0. */
12556         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12557       } else {
12558         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12559                 seconds, inc/1000);
12560       }
12561     }
12562     SendToProgram(buf, cps);
12563
12564     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12565     /* Orthogonally, limit search to given depth */
12566     if (sd > 0) {
12567       if (cps->sdKludge) {
12568         sprintf(buf, "depth\n%d\n", sd);
12569       } else {
12570         sprintf(buf, "sd %d\n", sd);
12571       }
12572       SendToProgram(buf, cps);
12573     }
12574
12575     if(cps->nps > 0) { /* [HGM] nps */
12576         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12577         else {
12578                 sprintf(buf, "nps %d\n", cps->nps);
12579               SendToProgram(buf, cps);
12580         }
12581     }
12582 }
12583
12584 ChessProgramState *WhitePlayer()
12585 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12586 {
12587     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12588        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12589         return &second;
12590     return &first;
12591 }
12592
12593 void
12594 SendTimeRemaining(cps, machineWhite)
12595      ChessProgramState *cps;
12596      int /*boolean*/ machineWhite;
12597 {
12598     char message[MSG_SIZ];
12599     long time, otime;
12600
12601     /* Note: this routine must be called when the clocks are stopped
12602        or when they have *just* been set or switched; otherwise
12603        it will be off by the time since the current tick started.
12604     */
12605     if (machineWhite) {
12606         time = whiteTimeRemaining / 10;
12607         otime = blackTimeRemaining / 10;
12608     } else {
12609         time = blackTimeRemaining / 10;
12610         otime = whiteTimeRemaining / 10;
12611     }
12612     /* [HGM] translate opponent's time by time-odds factor */
12613     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12614     if (appData.debugMode) {
12615         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12616     }
12617
12618     if (time <= 0) time = 1;
12619     if (otime <= 0) otime = 1;
12620     
12621     sprintf(message, "time %ld\n", time);
12622     SendToProgram(message, cps);
12623
12624     sprintf(message, "otim %ld\n", otime);
12625     SendToProgram(message, cps);
12626 }
12627
12628 int
12629 BoolFeature(p, name, loc, cps)
12630      char **p;
12631      char *name;
12632      int *loc;
12633      ChessProgramState *cps;
12634 {
12635   char buf[MSG_SIZ];
12636   int len = strlen(name);
12637   int val;
12638   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12639     (*p) += len + 1;
12640     sscanf(*p, "%d", &val);
12641     *loc = (val != 0);
12642     while (**p && **p != ' ') (*p)++;
12643     sprintf(buf, "accepted %s\n", name);
12644     SendToProgram(buf, cps);
12645     return TRUE;
12646   }
12647   return FALSE;
12648 }
12649
12650 int
12651 IntFeature(p, name, loc, cps)
12652      char **p;
12653      char *name;
12654      int *loc;
12655      ChessProgramState *cps;
12656 {
12657   char buf[MSG_SIZ];
12658   int len = strlen(name);
12659   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12660     (*p) += len + 1;
12661     sscanf(*p, "%d", loc);
12662     while (**p && **p != ' ') (*p)++;
12663     sprintf(buf, "accepted %s\n", name);
12664     SendToProgram(buf, cps);
12665     return TRUE;
12666   }
12667   return FALSE;
12668 }
12669
12670 int
12671 StringFeature(p, name, loc, cps)
12672      char **p;
12673      char *name;
12674      char loc[];
12675      ChessProgramState *cps;
12676 {
12677   char buf[MSG_SIZ];
12678   int len = strlen(name);
12679   if (strncmp((*p), name, len) == 0
12680       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12681     (*p) += len + 2;
12682     sscanf(*p, "%[^\"]", loc);
12683     while (**p && **p != '\"') (*p)++;
12684     if (**p == '\"') (*p)++;
12685     sprintf(buf, "accepted %s\n", name);
12686     SendToProgram(buf, cps);
12687     return TRUE;
12688   }
12689   return FALSE;
12690 }
12691
12692 int 
12693 ParseOption(Option *opt, ChessProgramState *cps)
12694 // [HGM] options: process the string that defines an engine option, and determine
12695 // name, type, default value, and allowed value range
12696 {
12697         char *p, *q, buf[MSG_SIZ];
12698         int n, min = (-1)<<31, max = 1<<31, def;
12699
12700         if(p = strstr(opt->name, " -spin ")) {
12701             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12702             if(max < min) max = min; // enforce consistency
12703             if(def < min) def = min;
12704             if(def > max) def = max;
12705             opt->value = def;
12706             opt->min = min;
12707             opt->max = max;
12708             opt->type = Spin;
12709         } else if((p = strstr(opt->name, " -slider "))) {
12710             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12711             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12712             if(max < min) max = min; // enforce consistency
12713             if(def < min) def = min;
12714             if(def > max) def = max;
12715             opt->value = def;
12716             opt->min = min;
12717             opt->max = max;
12718             opt->type = Spin; // Slider;
12719         } else if((p = strstr(opt->name, " -string "))) {
12720             opt->textValue = p+9;
12721             opt->type = TextBox;
12722         } else if((p = strstr(opt->name, " -file "))) {
12723             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12724             opt->textValue = p+7;
12725             opt->type = TextBox; // FileName;
12726         } else if((p = strstr(opt->name, " -path "))) {
12727             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12728             opt->textValue = p+7;
12729             opt->type = TextBox; // PathName;
12730         } else if(p = strstr(opt->name, " -check ")) {
12731             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12732             opt->value = (def != 0);
12733             opt->type = CheckBox;
12734         } else if(p = strstr(opt->name, " -combo ")) {
12735             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12736             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12737             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12738             opt->value = n = 0;
12739             while(q = StrStr(q, " /// ")) {
12740                 n++; *q = 0;    // count choices, and null-terminate each of them
12741                 q += 5;
12742                 if(*q == '*') { // remember default, which is marked with * prefix
12743                     q++;
12744                     opt->value = n;
12745                 }
12746                 cps->comboList[cps->comboCnt++] = q;
12747             }
12748             cps->comboList[cps->comboCnt++] = NULL;
12749             opt->max = n + 1;
12750             opt->type = ComboBox;
12751         } else if(p = strstr(opt->name, " -button")) {
12752             opt->type = Button;
12753         } else if(p = strstr(opt->name, " -save")) {
12754             opt->type = SaveButton;
12755         } else return FALSE;
12756         *p = 0; // terminate option name
12757         // now look if the command-line options define a setting for this engine option.
12758         if(cps->optionSettings && cps->optionSettings[0])
12759             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12760         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12761                 sprintf(buf, "option %s", p);
12762                 if(p = strstr(buf, ",")) *p = 0;
12763                 strcat(buf, "\n");
12764                 SendToProgram(buf, cps);
12765         }
12766         return TRUE;
12767 }
12768
12769 void
12770 FeatureDone(cps, val)
12771      ChessProgramState* cps;
12772      int val;
12773 {
12774   DelayedEventCallback cb = GetDelayedEvent();
12775   if ((cb == InitBackEnd3 && cps == &first) ||
12776       (cb == TwoMachinesEventIfReady && cps == &second)) {
12777     CancelDelayedEvent();
12778     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12779   }
12780   cps->initDone = val;
12781 }
12782
12783 /* Parse feature command from engine */
12784 void
12785 ParseFeatures(args, cps)
12786      char* args;
12787      ChessProgramState *cps;  
12788 {
12789   char *p = args;
12790   char *q;
12791   int val;
12792   char buf[MSG_SIZ];
12793
12794   for (;;) {
12795     while (*p == ' ') p++;
12796     if (*p == NULLCHAR) return;
12797
12798     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12799     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12800     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12801     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12802     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12803     if (BoolFeature(&p, "reuse", &val, cps)) {
12804       /* Engine can disable reuse, but can't enable it if user said no */
12805       if (!val) cps->reuse = FALSE;
12806       continue;
12807     }
12808     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12809     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12810       if (gameMode == TwoMachinesPlay) {
12811         DisplayTwoMachinesTitle();
12812       } else {
12813         DisplayTitle("");
12814       }
12815       continue;
12816     }
12817     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12818     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12819     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12820     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12821     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12822     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12823     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12824     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12825     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12826     if (IntFeature(&p, "done", &val, cps)) {
12827       FeatureDone(cps, val);
12828       continue;
12829     }
12830     /* Added by Tord: */
12831     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12832     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12833     /* End of additions by Tord */
12834
12835     /* [HGM] added features: */
12836     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12837     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12838     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12839     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12840     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12841     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12842     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12843         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12844             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12845             SendToProgram(buf, cps);
12846             continue;
12847         }
12848         if(cps->nrOptions >= MAX_OPTIONS) {
12849             cps->nrOptions--;
12850             sprintf(buf, "%s engine has too many options\n", cps->which);
12851             DisplayError(buf, 0);
12852         }
12853         continue;
12854     }
12855     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12856     /* End of additions by HGM */
12857
12858     /* unknown feature: complain and skip */
12859     q = p;
12860     while (*q && *q != '=') q++;
12861     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12862     SendToProgram(buf, cps);
12863     p = q;
12864     if (*p == '=') {
12865       p++;
12866       if (*p == '\"') {
12867         p++;
12868         while (*p && *p != '\"') p++;
12869         if (*p == '\"') p++;
12870       } else {
12871         while (*p && *p != ' ') p++;
12872       }
12873     }
12874   }
12875
12876 }
12877
12878 void
12879 PeriodicUpdatesEvent(newState)
12880      int newState;
12881 {
12882     if (newState == appData.periodicUpdates)
12883       return;
12884
12885     appData.periodicUpdates=newState;
12886
12887     /* Display type changes, so update it now */
12888 //    DisplayAnalysis();
12889
12890     /* Get the ball rolling again... */
12891     if (newState) {
12892         AnalysisPeriodicEvent(1);
12893         StartAnalysisClock();
12894     }
12895 }
12896
12897 void
12898 PonderNextMoveEvent(newState)
12899      int newState;
12900 {
12901     if (newState == appData.ponderNextMove) return;
12902     if (gameMode == EditPosition) EditPositionDone(TRUE);
12903     if (newState) {
12904         SendToProgram("hard\n", &first);
12905         if (gameMode == TwoMachinesPlay) {
12906             SendToProgram("hard\n", &second);
12907         }
12908     } else {
12909         SendToProgram("easy\n", &first);
12910         thinkOutput[0] = NULLCHAR;
12911         if (gameMode == TwoMachinesPlay) {
12912             SendToProgram("easy\n", &second);
12913         }
12914     }
12915     appData.ponderNextMove = newState;
12916 }
12917
12918 void
12919 NewSettingEvent(option, command, value)
12920      char *command;
12921      int option, value;
12922 {
12923     char buf[MSG_SIZ];
12924
12925     if (gameMode == EditPosition) EditPositionDone(TRUE);
12926     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12927     SendToProgram(buf, &first);
12928     if (gameMode == TwoMachinesPlay) {
12929         SendToProgram(buf, &second);
12930     }
12931 }
12932
12933 void
12934 ShowThinkingEvent()
12935 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12936 {
12937     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12938     int newState = appData.showThinking
12939         // [HGM] thinking: other features now need thinking output as well
12940         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12941     
12942     if (oldState == newState) return;
12943     oldState = newState;
12944     if (gameMode == EditPosition) EditPositionDone(TRUE);
12945     if (oldState) {
12946         SendToProgram("post\n", &first);
12947         if (gameMode == TwoMachinesPlay) {
12948             SendToProgram("post\n", &second);
12949         }
12950     } else {
12951         SendToProgram("nopost\n", &first);
12952         thinkOutput[0] = NULLCHAR;
12953         if (gameMode == TwoMachinesPlay) {
12954             SendToProgram("nopost\n", &second);
12955         }
12956     }
12957 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12958 }
12959
12960 void
12961 AskQuestionEvent(title, question, replyPrefix, which)
12962      char *title; char *question; char *replyPrefix; char *which;
12963 {
12964   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12965   if (pr == NoProc) return;
12966   AskQuestion(title, question, replyPrefix, pr);
12967 }
12968
12969 void
12970 DisplayMove(moveNumber)
12971      int moveNumber;
12972 {
12973     char message[MSG_SIZ];
12974     char res[MSG_SIZ];
12975     char cpThinkOutput[MSG_SIZ];
12976
12977     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12978     
12979     if (moveNumber == forwardMostMove - 1 || 
12980         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12981
12982         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12983
12984         if (strchr(cpThinkOutput, '\n')) {
12985             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12986         }
12987     } else {
12988         *cpThinkOutput = NULLCHAR;
12989     }
12990
12991     /* [AS] Hide thinking from human user */
12992     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12993         *cpThinkOutput = NULLCHAR;
12994         if( thinkOutput[0] != NULLCHAR ) {
12995             int i;
12996
12997             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12998                 cpThinkOutput[i] = '.';
12999             }
13000             cpThinkOutput[i] = NULLCHAR;
13001             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13002         }
13003     }
13004
13005     if (moveNumber == forwardMostMove - 1 &&
13006         gameInfo.resultDetails != NULL) {
13007         if (gameInfo.resultDetails[0] == NULLCHAR) {
13008             sprintf(res, " %s", PGNResult(gameInfo.result));
13009         } else {
13010             sprintf(res, " {%s} %s",
13011                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13012         }
13013     } else {
13014         res[0] = NULLCHAR;
13015     }
13016
13017     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13018         DisplayMessage(res, cpThinkOutput);
13019     } else {
13020         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13021                 WhiteOnMove(moveNumber) ? " " : ".. ",
13022                 parseList[moveNumber], res);
13023         DisplayMessage(message, cpThinkOutput);
13024     }
13025 }
13026
13027 void
13028 DisplayComment(moveNumber, text)
13029      int moveNumber;
13030      char *text;
13031 {
13032     char title[MSG_SIZ];
13033     char buf[8000]; // comment can be long!
13034     int score, depth;
13035     
13036     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13037       strcpy(title, "Comment");
13038     } else {
13039       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13040               WhiteOnMove(moveNumber) ? " " : ".. ",
13041               parseList[moveNumber]);
13042     }
13043     // [HGM] PV info: display PV info together with (or as) comment
13044     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13045       if(text == NULL) text = "";                                           
13046       score = pvInfoList[moveNumber].score;
13047       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13048               depth, (pvInfoList[moveNumber].time+50)/100, text);
13049       text = buf;
13050     }
13051     if (text != NULL && (appData.autoDisplayComment || commentUp))
13052         CommentPopUp(title, text);
13053 }
13054
13055 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13056  * might be busy thinking or pondering.  It can be omitted if your
13057  * gnuchess is configured to stop thinking immediately on any user
13058  * input.  However, that gnuchess feature depends on the FIONREAD
13059  * ioctl, which does not work properly on some flavors of Unix.
13060  */
13061 void
13062 Attention(cps)
13063      ChessProgramState *cps;
13064 {
13065 #if ATTENTION
13066     if (!cps->useSigint) return;
13067     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13068     switch (gameMode) {
13069       case MachinePlaysWhite:
13070       case MachinePlaysBlack:
13071       case TwoMachinesPlay:
13072       case IcsPlayingWhite:
13073       case IcsPlayingBlack:
13074       case AnalyzeMode:
13075       case AnalyzeFile:
13076         /* Skip if we know it isn't thinking */
13077         if (!cps->maybeThinking) return;
13078         if (appData.debugMode)
13079           fprintf(debugFP, "Interrupting %s\n", cps->which);
13080         InterruptChildProcess(cps->pr);
13081         cps->maybeThinking = FALSE;
13082         break;
13083       default:
13084         break;
13085     }
13086 #endif /*ATTENTION*/
13087 }
13088
13089 int
13090 CheckFlags()
13091 {
13092     if (whiteTimeRemaining <= 0) {
13093         if (!whiteFlag) {
13094             whiteFlag = TRUE;
13095             if (appData.icsActive) {
13096                 if (appData.autoCallFlag &&
13097                     gameMode == IcsPlayingBlack && !blackFlag) {
13098                   SendToICS(ics_prefix);
13099                   SendToICS("flag\n");
13100                 }
13101             } else {
13102                 if (blackFlag) {
13103                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13104                 } else {
13105                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13106                     if (appData.autoCallFlag) {
13107                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13108                         return TRUE;
13109                     }
13110                 }
13111             }
13112         }
13113     }
13114     if (blackTimeRemaining <= 0) {
13115         if (!blackFlag) {
13116             blackFlag = TRUE;
13117             if (appData.icsActive) {
13118                 if (appData.autoCallFlag &&
13119                     gameMode == IcsPlayingWhite && !whiteFlag) {
13120                   SendToICS(ics_prefix);
13121                   SendToICS("flag\n");
13122                 }
13123             } else {
13124                 if (whiteFlag) {
13125                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13126                 } else {
13127                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13128                     if (appData.autoCallFlag) {
13129                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13130                         return TRUE;
13131                     }
13132                 }
13133             }
13134         }
13135     }
13136     return FALSE;
13137 }
13138
13139 void
13140 CheckTimeControl()
13141 {
13142     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13143         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13144
13145     /*
13146      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13147      */
13148     if ( !WhiteOnMove(forwardMostMove) )
13149         /* White made time control */
13150         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13151         /* [HGM] time odds: correct new time quota for time odds! */
13152                                             / WhitePlayer()->timeOdds;
13153       else
13154         /* Black made time control */
13155         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13156                                             / WhitePlayer()->other->timeOdds;
13157 }
13158
13159 void
13160 DisplayBothClocks()
13161 {
13162     int wom = gameMode == EditPosition ?
13163       !blackPlaysFirst : WhiteOnMove(currentMove);
13164     DisplayWhiteClock(whiteTimeRemaining, wom);
13165     DisplayBlackClock(blackTimeRemaining, !wom);
13166 }
13167
13168
13169 /* Timekeeping seems to be a portability nightmare.  I think everyone
13170    has ftime(), but I'm really not sure, so I'm including some ifdefs
13171    to use other calls if you don't.  Clocks will be less accurate if
13172    you have neither ftime nor gettimeofday.
13173 */
13174
13175 /* VS 2008 requires the #include outside of the function */
13176 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13177 #include <sys/timeb.h>
13178 #endif
13179
13180 /* Get the current time as a TimeMark */
13181 void
13182 GetTimeMark(tm)
13183      TimeMark *tm;
13184 {
13185 #if HAVE_GETTIMEOFDAY
13186
13187     struct timeval timeVal;
13188     struct timezone timeZone;
13189
13190     gettimeofday(&timeVal, &timeZone);
13191     tm->sec = (long) timeVal.tv_sec; 
13192     tm->ms = (int) (timeVal.tv_usec / 1000L);
13193
13194 #else /*!HAVE_GETTIMEOFDAY*/
13195 #if HAVE_FTIME
13196
13197 // include <sys/timeb.h> / moved to just above start of function
13198     struct timeb timeB;
13199
13200     ftime(&timeB);
13201     tm->sec = (long) timeB.time;
13202     tm->ms = (int) timeB.millitm;
13203
13204 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13205     tm->sec = (long) time(NULL);
13206     tm->ms = 0;
13207 #endif
13208 #endif
13209 }
13210
13211 /* Return the difference in milliseconds between two
13212    time marks.  We assume the difference will fit in a long!
13213 */
13214 long
13215 SubtractTimeMarks(tm2, tm1)
13216      TimeMark *tm2, *tm1;
13217 {
13218     return 1000L*(tm2->sec - tm1->sec) +
13219            (long) (tm2->ms - tm1->ms);
13220 }
13221
13222
13223 /*
13224  * Code to manage the game clocks.
13225  *
13226  * In tournament play, black starts the clock and then white makes a move.
13227  * We give the human user a slight advantage if he is playing white---the
13228  * clocks don't run until he makes his first move, so it takes zero time.
13229  * Also, we don't account for network lag, so we could get out of sync
13230  * with GNU Chess's clock -- but then, referees are always right.  
13231  */
13232
13233 static TimeMark tickStartTM;
13234 static long intendedTickLength;
13235
13236 long
13237 NextTickLength(timeRemaining)
13238      long timeRemaining;
13239 {
13240     long nominalTickLength, nextTickLength;
13241
13242     if (timeRemaining > 0L && timeRemaining <= 10000L)
13243       nominalTickLength = 100L;
13244     else
13245       nominalTickLength = 1000L;
13246     nextTickLength = timeRemaining % nominalTickLength;
13247     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13248
13249     return nextTickLength;
13250 }
13251
13252 /* Adjust clock one minute up or down */
13253 void
13254 AdjustClock(Boolean which, int dir)
13255 {
13256     if(which) blackTimeRemaining += 60000*dir;
13257     else      whiteTimeRemaining += 60000*dir;
13258     DisplayBothClocks();
13259 }
13260
13261 /* Stop clocks and reset to a fresh time control */
13262 void
13263 ResetClocks() 
13264 {
13265     (void) StopClockTimer();
13266     if (appData.icsActive) {
13267         whiteTimeRemaining = blackTimeRemaining = 0;
13268     } else if (searchTime) {
13269         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13270         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13271     } else { /* [HGM] correct new time quote for time odds */
13272         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13273         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13274     }
13275     if (whiteFlag || blackFlag) {
13276         DisplayTitle("");
13277         whiteFlag = blackFlag = FALSE;
13278     }
13279     DisplayBothClocks();
13280 }
13281
13282 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13283
13284 /* Decrement running clock by amount of time that has passed */
13285 void
13286 DecrementClocks()
13287 {
13288     long timeRemaining;
13289     long lastTickLength, fudge;
13290     TimeMark now;
13291
13292     if (!appData.clockMode) return;
13293     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13294         
13295     GetTimeMark(&now);
13296
13297     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13298
13299     /* Fudge if we woke up a little too soon */
13300     fudge = intendedTickLength - lastTickLength;
13301     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13302
13303     if (WhiteOnMove(forwardMostMove)) {
13304         if(whiteNPS >= 0) lastTickLength = 0;
13305         timeRemaining = whiteTimeRemaining -= lastTickLength;
13306         DisplayWhiteClock(whiteTimeRemaining - fudge,
13307                           WhiteOnMove(currentMove));
13308     } else {
13309         if(blackNPS >= 0) lastTickLength = 0;
13310         timeRemaining = blackTimeRemaining -= lastTickLength;
13311         DisplayBlackClock(blackTimeRemaining - fudge,
13312                           !WhiteOnMove(currentMove));
13313     }
13314
13315     if (CheckFlags()) return;
13316         
13317     tickStartTM = now;
13318     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13319     StartClockTimer(intendedTickLength);
13320
13321     /* if the time remaining has fallen below the alarm threshold, sound the
13322      * alarm. if the alarm has sounded and (due to a takeback or time control
13323      * with increment) the time remaining has increased to a level above the
13324      * threshold, reset the alarm so it can sound again. 
13325      */
13326     
13327     if (appData.icsActive && appData.icsAlarm) {
13328
13329         /* make sure we are dealing with the user's clock */
13330         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13331                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13332            )) return;
13333
13334         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13335             alarmSounded = FALSE;
13336         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13337             PlayAlarmSound();
13338             alarmSounded = TRUE;
13339         }
13340     }
13341 }
13342
13343
13344 /* A player has just moved, so stop the previously running
13345    clock and (if in clock mode) start the other one.
13346    We redisplay both clocks in case we're in ICS mode, because
13347    ICS gives us an update to both clocks after every move.
13348    Note that this routine is called *after* forwardMostMove
13349    is updated, so the last fractional tick must be subtracted
13350    from the color that is *not* on move now.
13351 */
13352 void
13353 SwitchClocks()
13354 {
13355     long lastTickLength;
13356     TimeMark now;
13357     int flagged = FALSE;
13358
13359     GetTimeMark(&now);
13360
13361     if (StopClockTimer() && appData.clockMode) {
13362         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13363         if (WhiteOnMove(forwardMostMove)) {
13364             if(blackNPS >= 0) lastTickLength = 0;
13365             blackTimeRemaining -= lastTickLength;
13366            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13367 //         if(pvInfoList[forwardMostMove-1].time == -1)
13368                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13369                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13370         } else {
13371            if(whiteNPS >= 0) lastTickLength = 0;
13372            whiteTimeRemaining -= lastTickLength;
13373            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13374 //         if(pvInfoList[forwardMostMove-1].time == -1)
13375                  pvInfoList[forwardMostMove-1].time = 
13376                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13377         }
13378         flagged = CheckFlags();
13379     }
13380     CheckTimeControl();
13381
13382     if (flagged || !appData.clockMode) return;
13383
13384     switch (gameMode) {
13385       case MachinePlaysBlack:
13386       case MachinePlaysWhite:
13387       case BeginningOfGame:
13388         if (pausing) return;
13389         break;
13390
13391       case EditGame:
13392       case PlayFromGameFile:
13393       case IcsExamining:
13394         return;
13395
13396       default:
13397         break;
13398     }
13399
13400     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13401         if(WhiteOnMove(forwardMostMove))
13402              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13403         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13404     }
13405
13406     tickStartTM = now;
13407     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13408       whiteTimeRemaining : blackTimeRemaining);
13409     StartClockTimer(intendedTickLength);
13410 }
13411         
13412
13413 /* Stop both clocks */
13414 void
13415 StopClocks()
13416 {       
13417     long lastTickLength;
13418     TimeMark now;
13419
13420     if (!StopClockTimer()) return;
13421     if (!appData.clockMode) return;
13422
13423     GetTimeMark(&now);
13424
13425     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13426     if (WhiteOnMove(forwardMostMove)) {
13427         if(whiteNPS >= 0) lastTickLength = 0;
13428         whiteTimeRemaining -= lastTickLength;
13429         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13430     } else {
13431         if(blackNPS >= 0) lastTickLength = 0;
13432         blackTimeRemaining -= lastTickLength;
13433         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13434     }
13435     CheckFlags();
13436 }
13437         
13438 /* Start clock of player on move.  Time may have been reset, so
13439    if clock is already running, stop and restart it. */
13440 void
13441 StartClocks()
13442 {
13443     (void) StopClockTimer(); /* in case it was running already */
13444     DisplayBothClocks();
13445     if (CheckFlags()) return;
13446
13447     if (!appData.clockMode) return;
13448     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13449
13450     GetTimeMark(&tickStartTM);
13451     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13452       whiteTimeRemaining : blackTimeRemaining);
13453
13454    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13455     whiteNPS = blackNPS = -1; 
13456     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13457        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13458         whiteNPS = first.nps;
13459     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13460        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13461         blackNPS = first.nps;
13462     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13463         whiteNPS = second.nps;
13464     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13465         blackNPS = second.nps;
13466     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13467
13468     StartClockTimer(intendedTickLength);
13469 }
13470
13471 char *
13472 TimeString(ms)
13473      long ms;
13474 {
13475     long second, minute, hour, day;
13476     char *sign = "";
13477     static char buf[32];
13478     
13479     if (ms > 0 && ms <= 9900) {
13480       /* convert milliseconds to tenths, rounding up */
13481       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13482
13483       sprintf(buf, " %03.1f ", tenths/10.0);
13484       return buf;
13485     }
13486
13487     /* convert milliseconds to seconds, rounding up */
13488     /* use floating point to avoid strangeness of integer division
13489        with negative dividends on many machines */
13490     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13491
13492     if (second < 0) {
13493         sign = "-";
13494         second = -second;
13495     }
13496     
13497     day = second / (60 * 60 * 24);
13498     second = second % (60 * 60 * 24);
13499     hour = second / (60 * 60);
13500     second = second % (60 * 60);
13501     minute = second / 60;
13502     second = second % 60;
13503     
13504     if (day > 0)
13505       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13506               sign, day, hour, minute, second);
13507     else if (hour > 0)
13508       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13509     else
13510       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13511     
13512     return buf;
13513 }
13514
13515
13516 /*
13517  * This is necessary because some C libraries aren't ANSI C compliant yet.
13518  */
13519 char *
13520 StrStr(string, match)
13521      char *string, *match;
13522 {
13523     int i, length;
13524     
13525     length = strlen(match);
13526     
13527     for (i = strlen(string) - length; i >= 0; i--, string++)
13528       if (!strncmp(match, string, length))
13529         return string;
13530     
13531     return NULL;
13532 }
13533
13534 char *
13535 StrCaseStr(string, match)
13536      char *string, *match;
13537 {
13538     int i, j, length;
13539     
13540     length = strlen(match);
13541     
13542     for (i = strlen(string) - length; i >= 0; i--, string++) {
13543         for (j = 0; j < length; j++) {
13544             if (ToLower(match[j]) != ToLower(string[j]))
13545               break;
13546         }
13547         if (j == length) return string;
13548     }
13549
13550     return NULL;
13551 }
13552
13553 #ifndef _amigados
13554 int
13555 StrCaseCmp(s1, s2)
13556      char *s1, *s2;
13557 {
13558     char c1, c2;
13559     
13560     for (;;) {
13561         c1 = ToLower(*s1++);
13562         c2 = ToLower(*s2++);
13563         if (c1 > c2) return 1;
13564         if (c1 < c2) return -1;
13565         if (c1 == NULLCHAR) return 0;
13566     }
13567 }
13568
13569
13570 int
13571 ToLower(c)
13572      int c;
13573 {
13574     return isupper(c) ? tolower(c) : c;
13575 }
13576
13577
13578 int
13579 ToUpper(c)
13580      int c;
13581 {
13582     return islower(c) ? toupper(c) : c;
13583 }
13584 #endif /* !_amigados    */
13585
13586 char *
13587 StrSave(s)
13588      char *s;
13589 {
13590     char *ret;
13591
13592     if ((ret = (char *) malloc(strlen(s) + 1))) {
13593         strcpy(ret, s);
13594     }
13595     return ret;
13596 }
13597
13598 char *
13599 StrSavePtr(s, savePtr)
13600      char *s, **savePtr;
13601 {
13602     if (*savePtr) {
13603         free(*savePtr);
13604     }
13605     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13606         strcpy(*savePtr, s);
13607     }
13608     return(*savePtr);
13609 }
13610
13611 char *
13612 PGNDate()
13613 {
13614     time_t clock;
13615     struct tm *tm;
13616     char buf[MSG_SIZ];
13617
13618     clock = time((time_t *)NULL);
13619     tm = localtime(&clock);
13620     sprintf(buf, "%04d.%02d.%02d",
13621             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13622     return StrSave(buf);
13623 }
13624
13625
13626 char *
13627 PositionToFEN(move, overrideCastling)
13628      int move;
13629      char *overrideCastling;
13630 {
13631     int i, j, fromX, fromY, toX, toY;
13632     int whiteToPlay;
13633     char buf[128];
13634     char *p, *q;
13635     int emptycount;
13636     ChessSquare piece;
13637
13638     whiteToPlay = (gameMode == EditPosition) ?
13639       !blackPlaysFirst : (move % 2 == 0);
13640     p = buf;
13641
13642     /* Piece placement data */
13643     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13644         emptycount = 0;
13645         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13646             if (boards[move][i][j] == EmptySquare) {
13647                 emptycount++;
13648             } else { ChessSquare piece = boards[move][i][j];
13649                 if (emptycount > 0) {
13650                     if(emptycount<10) /* [HGM] can be >= 10 */
13651                         *p++ = '0' + emptycount;
13652                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13653                     emptycount = 0;
13654                 }
13655                 if(PieceToChar(piece) == '+') {
13656                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13657                     *p++ = '+';
13658                     piece = (ChessSquare)(DEMOTED piece);
13659                 } 
13660                 *p++ = PieceToChar(piece);
13661                 if(p[-1] == '~') {
13662                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13663                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13664                     *p++ = '~';
13665                 }
13666             }
13667         }
13668         if (emptycount > 0) {
13669             if(emptycount<10) /* [HGM] can be >= 10 */
13670                 *p++ = '0' + emptycount;
13671             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13672             emptycount = 0;
13673         }
13674         *p++ = '/';
13675     }
13676     *(p - 1) = ' ';
13677
13678     /* [HGM] print Crazyhouse or Shogi holdings */
13679     if( gameInfo.holdingsWidth ) {
13680         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13681         q = p;
13682         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13683             piece = boards[move][i][BOARD_WIDTH-1];
13684             if( piece != EmptySquare )
13685               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13686                   *p++ = PieceToChar(piece);
13687         }
13688         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13689             piece = boards[move][BOARD_HEIGHT-i-1][0];
13690             if( piece != EmptySquare )
13691               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13692                   *p++ = PieceToChar(piece);
13693         }
13694
13695         if( q == p ) *p++ = '-';
13696         *p++ = ']';
13697         *p++ = ' ';
13698     }
13699
13700     /* Active color */
13701     *p++ = whiteToPlay ? 'w' : 'b';
13702     *p++ = ' ';
13703
13704   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13705     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13706   } else {
13707   if(nrCastlingRights) {
13708      q = p;
13709      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13710        /* [HGM] write directly from rights */
13711            if(boards[move][CASTLING][2] != NoRights &&
13712               boards[move][CASTLING][0] != NoRights   )
13713                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13714            if(boards[move][CASTLING][2] != NoRights &&
13715               boards[move][CASTLING][1] != NoRights   )
13716                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13717            if(boards[move][CASTLING][5] != NoRights &&
13718               boards[move][CASTLING][3] != NoRights   )
13719                 *p++ = boards[move][CASTLING][3] + AAA;
13720            if(boards[move][CASTLING][5] != NoRights &&
13721               boards[move][CASTLING][4] != NoRights   )
13722                 *p++ = boards[move][CASTLING][4] + AAA;
13723      } else {
13724
13725         /* [HGM] write true castling rights */
13726         if( nrCastlingRights == 6 ) {
13727             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13728                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13729             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13730                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13731             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13732                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13733             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13734                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13735         }
13736      }
13737      if (q == p) *p++ = '-'; /* No castling rights */
13738      *p++ = ' ';
13739   }
13740
13741   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13742      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13743     /* En passant target square */
13744     if (move > backwardMostMove) {
13745         fromX = moveList[move - 1][0] - AAA;
13746         fromY = moveList[move - 1][1] - ONE;
13747         toX = moveList[move - 1][2] - AAA;
13748         toY = moveList[move - 1][3] - ONE;
13749         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13750             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13751             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13752             fromX == toX) {
13753             /* 2-square pawn move just happened */
13754             *p++ = toX + AAA;
13755             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13756         } else {
13757             *p++ = '-';
13758         }
13759     } else if(move == backwardMostMove) {
13760         // [HGM] perhaps we should always do it like this, and forget the above?
13761         if((signed char)boards[move][EP_STATUS] >= 0) {
13762             *p++ = boards[move][EP_STATUS] + AAA;
13763             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13764         } else {
13765             *p++ = '-';
13766         }
13767     } else {
13768         *p++ = '-';
13769     }
13770     *p++ = ' ';
13771   }
13772   }
13773
13774     /* [HGM] find reversible plies */
13775     {   int i = 0, j=move;
13776
13777         if (appData.debugMode) { int k;
13778             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13779             for(k=backwardMostMove; k<=forwardMostMove; k++)
13780                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13781
13782         }
13783
13784         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13785         if( j == backwardMostMove ) i += initialRulePlies;
13786         sprintf(p, "%d ", i);
13787         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13788     }
13789     /* Fullmove number */
13790     sprintf(p, "%d", (move / 2) + 1);
13791     
13792     return StrSave(buf);
13793 }
13794
13795 Boolean
13796 ParseFEN(board, blackPlaysFirst, fen)
13797     Board board;
13798      int *blackPlaysFirst;
13799      char *fen;
13800 {
13801     int i, j;
13802     char *p;
13803     int emptycount;
13804     ChessSquare piece;
13805
13806     p = fen;
13807
13808     /* [HGM] by default clear Crazyhouse holdings, if present */
13809     if(gameInfo.holdingsWidth) {
13810        for(i=0; i<BOARD_HEIGHT; i++) {
13811            board[i][0]             = EmptySquare; /* black holdings */
13812            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13813            board[i][1]             = (ChessSquare) 0; /* black counts */
13814            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13815        }
13816     }
13817
13818     /* Piece placement data */
13819     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13820         j = 0;
13821         for (;;) {
13822             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13823                 if (*p == '/') p++;
13824                 emptycount = gameInfo.boardWidth - j;
13825                 while (emptycount--)
13826                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13827                 break;
13828 #if(BOARD_FILES >= 10)
13829             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13830                 p++; emptycount=10;
13831                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13832                 while (emptycount--)
13833                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13834 #endif
13835             } else if (isdigit(*p)) {
13836                 emptycount = *p++ - '0';
13837                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13838                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13839                 while (emptycount--)
13840                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13841             } else if (*p == '+' || isalpha(*p)) {
13842                 if (j >= gameInfo.boardWidth) return FALSE;
13843                 if(*p=='+') {
13844                     piece = CharToPiece(*++p);
13845                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13846                     piece = (ChessSquare) (PROMOTED piece ); p++;
13847                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13848                 } else piece = CharToPiece(*p++);
13849
13850                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13851                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13852                     piece = (ChessSquare) (PROMOTED piece);
13853                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13854                     p++;
13855                 }
13856                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13857             } else {
13858                 return FALSE;
13859             }
13860         }
13861     }
13862     while (*p == '/' || *p == ' ') p++;
13863
13864     /* [HGM] look for Crazyhouse holdings here */
13865     while(*p==' ') p++;
13866     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13867         if(*p == '[') p++;
13868         if(*p == '-' ) *p++; /* empty holdings */ else {
13869             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13870             /* if we would allow FEN reading to set board size, we would   */
13871             /* have to add holdings and shift the board read so far here   */
13872             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13873                 *p++;
13874                 if((int) piece >= (int) BlackPawn ) {
13875                     i = (int)piece - (int)BlackPawn;
13876                     i = PieceToNumber((ChessSquare)i);
13877                     if( i >= gameInfo.holdingsSize ) return FALSE;
13878                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13879                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13880                 } else {
13881                     i = (int)piece - (int)WhitePawn;
13882                     i = PieceToNumber((ChessSquare)i);
13883                     if( i >= gameInfo.holdingsSize ) return FALSE;
13884                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13885                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13886                 }
13887             }
13888         }
13889         if(*p == ']') *p++;
13890     }
13891
13892     while(*p == ' ') p++;
13893
13894     /* Active color */
13895     switch (*p++) {
13896       case 'w':
13897         *blackPlaysFirst = FALSE;
13898         break;
13899       case 'b': 
13900         *blackPlaysFirst = TRUE;
13901         break;
13902       default:
13903         return FALSE;
13904     }
13905
13906     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13907     /* return the extra info in global variiables             */
13908
13909     /* set defaults in case FEN is incomplete */
13910     board[EP_STATUS] = EP_UNKNOWN;
13911     for(i=0; i<nrCastlingRights; i++ ) {
13912         board[CASTLING][i] =
13913             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13914     }   /* assume possible unless obviously impossible */
13915     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13916     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13917     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13918     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13919     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13920     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13921     FENrulePlies = 0;
13922
13923     while(*p==' ') p++;
13924     if(nrCastlingRights) {
13925       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13926           /* castling indicator present, so default becomes no castlings */
13927           for(i=0; i<nrCastlingRights; i++ ) {
13928                  board[CASTLING][i] = NoRights;
13929           }
13930       }
13931       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13932              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13933              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13934              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13935         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13936
13937         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13938             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13939             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13940         }
13941         switch(c) {
13942           case'K':
13943               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13944               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13945               board[CASTLING][2] = whiteKingFile;
13946               break;
13947           case'Q':
13948               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13949               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13950               board[CASTLING][2] = whiteKingFile;
13951               break;
13952           case'k':
13953               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13954               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13955               board[CASTLING][5] = blackKingFile;
13956               break;
13957           case'q':
13958               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13959               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13960               board[CASTLING][5] = blackKingFile;
13961           case '-':
13962               break;
13963           default: /* FRC castlings */
13964               if(c >= 'a') { /* black rights */
13965                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13966                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13967                   if(i == BOARD_RGHT) break;
13968                   board[CASTLING][5] = i;
13969                   c -= AAA;
13970                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13971                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13972                   if(c > i)
13973                       board[CASTLING][3] = c;
13974                   else
13975                       board[CASTLING][4] = c;
13976               } else { /* white rights */
13977                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13978                     if(board[0][i] == WhiteKing) break;
13979                   if(i == BOARD_RGHT) break;
13980                   board[CASTLING][2] = i;
13981                   c -= AAA - 'a' + 'A';
13982                   if(board[0][c] >= WhiteKing) break;
13983                   if(c > i)
13984                       board[CASTLING][0] = c;
13985                   else
13986                       board[CASTLING][1] = c;
13987               }
13988         }
13989       }
13990     if (appData.debugMode) {
13991         fprintf(debugFP, "FEN castling rights:");
13992         for(i=0; i<nrCastlingRights; i++)
13993         fprintf(debugFP, " %d", board[CASTLING][i]);
13994         fprintf(debugFP, "\n");
13995     }
13996
13997       while(*p==' ') p++;
13998     }
13999
14000     /* read e.p. field in games that know e.p. capture */
14001     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14002        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14003       if(*p=='-') {
14004         p++; board[EP_STATUS] = EP_NONE;
14005       } else {
14006          char c = *p++ - AAA;
14007
14008          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14009          if(*p >= '0' && *p <='9') *p++;
14010          board[EP_STATUS] = c;
14011       }
14012     }
14013
14014
14015     if(sscanf(p, "%d", &i) == 1) {
14016         FENrulePlies = i; /* 50-move ply counter */
14017         /* (The move number is still ignored)    */
14018     }
14019
14020     return TRUE;
14021 }
14022       
14023 void
14024 EditPositionPasteFEN(char *fen)
14025 {
14026   if (fen != NULL) {
14027     Board initial_position;
14028
14029     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14030       DisplayError(_("Bad FEN position in clipboard"), 0);
14031       return ;
14032     } else {
14033       int savedBlackPlaysFirst = blackPlaysFirst;
14034       EditPositionEvent();
14035       blackPlaysFirst = savedBlackPlaysFirst;
14036       CopyBoard(boards[0], initial_position);
14037       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14038       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14039       DisplayBothClocks();
14040       DrawPosition(FALSE, boards[currentMove]);
14041     }
14042   }
14043 }
14044
14045 static char cseq[12] = "\\   ";
14046
14047 Boolean set_cont_sequence(char *new_seq)
14048 {
14049     int len;
14050     Boolean ret;
14051
14052     // handle bad attempts to set the sequence
14053         if (!new_seq)
14054                 return 0; // acceptable error - no debug
14055
14056     len = strlen(new_seq);
14057     ret = (len > 0) && (len < sizeof(cseq));
14058     if (ret)
14059         strcpy(cseq, new_seq);
14060     else if (appData.debugMode)
14061         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14062     return ret;
14063 }
14064
14065 /*
14066     reformat a source message so words don't cross the width boundary.  internal
14067     newlines are not removed.  returns the wrapped size (no null character unless
14068     included in source message).  If dest is NULL, only calculate the size required
14069     for the dest buffer.  lp argument indicats line position upon entry, and it's
14070     passed back upon exit.
14071 */
14072 int wrap(char *dest, char *src, int count, int width, int *lp)
14073 {
14074     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14075
14076     cseq_len = strlen(cseq);
14077     old_line = line = *lp;
14078     ansi = len = clen = 0;
14079
14080     for (i=0; i < count; i++)
14081     {
14082         if (src[i] == '\033')
14083             ansi = 1;
14084
14085         // if we hit the width, back up
14086         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14087         {
14088             // store i & len in case the word is too long
14089             old_i = i, old_len = len;
14090
14091             // find the end of the last word
14092             while (i && src[i] != ' ' && src[i] != '\n')
14093             {
14094                 i--;
14095                 len--;
14096             }
14097
14098             // word too long?  restore i & len before splitting it
14099             if ((old_i-i+clen) >= width)
14100             {
14101                 i = old_i;
14102                 len = old_len;
14103             }
14104
14105             // extra space?
14106             if (i && src[i-1] == ' ')
14107                 len--;
14108
14109             if (src[i] != ' ' && src[i] != '\n')
14110             {
14111                 i--;
14112                 if (len)
14113                     len--;
14114             }
14115
14116             // now append the newline and continuation sequence
14117             if (dest)
14118                 dest[len] = '\n';
14119             len++;
14120             if (dest)
14121                 strncpy(dest+len, cseq, cseq_len);
14122             len += cseq_len;
14123             line = cseq_len;
14124             clen = cseq_len;
14125             continue;
14126         }
14127
14128         if (dest)
14129             dest[len] = src[i];
14130         len++;
14131         if (!ansi)
14132             line++;
14133         if (src[i] == '\n')
14134             line = 0;
14135         if (src[i] == 'm')
14136             ansi = 0;
14137     }
14138     if (dest && appData.debugMode)
14139     {
14140         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14141             count, width, line, len, *lp);
14142         show_bytes(debugFP, src, count);
14143         fprintf(debugFP, "\ndest: ");
14144         show_bytes(debugFP, dest, len);
14145         fprintf(debugFP, "\n");
14146     }
14147     *lp = dest ? line : old_line;
14148
14149     return len;
14150 }
14151
14152 // [HGM] vari: routines for shelving variations
14153
14154 void 
14155 PushTail(int firstMove, int lastMove)
14156 {
14157         int i, j, nrMoves = lastMove - firstMove;
14158
14159         if(appData.icsActive) { // only in local mode
14160                 forwardMostMove = currentMove; // mimic old ICS behavior
14161                 return;
14162         }
14163         if(storedGames >= MAX_VARIATIONS-1) return;
14164
14165         // push current tail of game on stack
14166         savedResult[storedGames] = gameInfo.result;
14167         savedDetails[storedGames] = gameInfo.resultDetails;
14168         gameInfo.resultDetails = NULL;
14169         savedFirst[storedGames] = firstMove;
14170         savedLast [storedGames] = lastMove;
14171         savedFramePtr[storedGames] = framePtr;
14172         framePtr -= nrMoves; // reserve space for the boards
14173         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14174             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14175             for(j=0; j<MOVE_LEN; j++)
14176                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14177             for(j=0; j<2*MOVE_LEN; j++)
14178                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14179             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14180             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14181             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14182             pvInfoList[firstMove+i-1].depth = 0;
14183             commentList[framePtr+i] = commentList[firstMove+i];
14184             commentList[firstMove+i] = NULL;
14185         }
14186
14187         storedGames++;
14188         forwardMostMove = currentMove; // truncte game so we can start variation
14189         if(storedGames == 1) GreyRevert(FALSE);
14190 }
14191
14192 Boolean
14193 PopTail(Boolean annotate)
14194 {
14195         int i, j, nrMoves;
14196         char buf[8000], moveBuf[20];
14197
14198         if(appData.icsActive) return FALSE; // only in local mode
14199         if(!storedGames) return FALSE; // sanity
14200
14201         storedGames--;
14202         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14203         nrMoves = savedLast[storedGames] - currentMove;
14204         if(annotate) {
14205                 int cnt = 10;
14206                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14207                 else strcpy(buf, "(");
14208                 for(i=currentMove; i<forwardMostMove; i++) {
14209                         if(WhiteOnMove(i))
14210                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14211                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14212                         strcat(buf, moveBuf);
14213                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14214                 }
14215                 strcat(buf, ")");
14216         }
14217         for(i=1; i<nrMoves; i++) { // copy last variation back
14218             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14219             for(j=0; j<MOVE_LEN; j++)
14220                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14221             for(j=0; j<2*MOVE_LEN; j++)
14222                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14223             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14224             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14225             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14226             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14227             commentList[currentMove+i] = commentList[framePtr+i];
14228             commentList[framePtr+i] = NULL;
14229         }
14230         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14231         framePtr = savedFramePtr[storedGames];
14232         gameInfo.result = savedResult[storedGames];
14233         if(gameInfo.resultDetails != NULL) {
14234             free(gameInfo.resultDetails);
14235       }
14236         gameInfo.resultDetails = savedDetails[storedGames];
14237         forwardMostMove = currentMove + nrMoves;
14238         if(storedGames == 0) GreyRevert(TRUE);
14239         return TRUE;
14240 }
14241
14242 void 
14243 CleanupTail()
14244 {       // remove all shelved variations
14245         int i;
14246         for(i=0; i<storedGames; i++) {
14247             if(savedDetails[i])
14248                 free(savedDetails[i]);
14249             savedDetails[i] = NULL;
14250         }
14251         for(i=framePtr; i<MAX_MOVES; i++) {
14252                 if(commentList[i]) free(commentList[i]);
14253                 commentList[i] = NULL;
14254         }
14255         framePtr = MAX_MOVES-1;
14256         storedGames = 0;
14257 }