Display PV right-clicked from EngineOutput window
[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 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
239 int endPV = -1;
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
243 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252
253 /* States for ics_getting_history */
254 #define H_FALSE 0
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
260
261 /* whosays values for GameEnds */
262 #define GE_ICS 0
263 #define GE_ENGINE 1
264 #define GE_PLAYER 2
265 #define GE_FILE 3
266 #define GE_XBOARD 4
267 #define GE_ENGINE1 5
268 #define GE_ENGINE2 6
269
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
272
273 /* Different types of move when calling RegisterMove */
274 #define CMAIL_MOVE   0
275 #define CMAIL_RESIGN 1
276 #define CMAIL_DRAW   2
277 #define CMAIL_ACCEPT 3
278
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
283
284 /* Telnet protocol constants */
285 #define TN_WILL 0373
286 #define TN_WONT 0374
287 #define TN_DO   0375
288 #define TN_DONT 0376
289 #define TN_IAC  0377
290 #define TN_ECHO 0001
291 #define TN_SGA  0003
292 #define TN_PORT 23
293
294 /* [AS] */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
296 {
297     assert( dst != NULL );
298     assert( src != NULL );
299     assert( count > 0 );
300
301     strncpy( dst, src, count );
302     dst[ count-1 ] = '\0';
303     return dst;
304 }
305
306 /* Some compiler can't cast u64 to double
307  * This function do the job for us:
308
309  * We use the highest bit for cast, this only
310  * works if the highest bit is not
311  * in use (This should not happen)
312  *
313  * We used this for all compiler
314  */
315 double
316 u64ToDouble(u64 value)
317 {
318   double r;
319   u64 tmp = value & u64Const(0x7fffffffffffffff);
320   r = (double)(s64)tmp;
321   if (value & u64Const(0x8000000000000000))
322        r +=  9.2233720368547758080e18; /* 2^63 */
323  return r;
324 }
325
326 /* Fake up flags for now, as we aren't keeping track of castling
327    availability yet. [HGM] Change of logic: the flag now only
328    indicates the type of castlings allowed by the rule of the game.
329    The actual rights themselves are maintained in the array
330    castlingRights, as part of the game history, and are not probed
331    by this function.
332  */
333 int
334 PosFlags(index)
335 {
336   int flags = F_ALL_CASTLE_OK;
337   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338   switch (gameInfo.variant) {
339   case VariantSuicide:
340     flags &= ~F_ALL_CASTLE_OK;
341   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342     flags |= F_IGNORE_CHECK;
343   case VariantLosers:
344     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345     break;
346   case VariantAtomic:
347     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
348     break;
349   case VariantKriegspiel:
350     flags |= F_KRIEGSPIEL_CAPTURE;
351     break;
352   case VariantCapaRandom: 
353   case VariantFischeRandom:
354     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355   case VariantNoCastle:
356   case VariantShatranj:
357   case VariantCourier:
358     flags &= ~F_ALL_CASTLE_OK;
359     break;
360   default:
361     break;
362   }
363   return flags;
364 }
365
366 FILE *gameFileFP, *debugFP;
367
368 /* 
369     [AS] Note: sometimes, the sscanf() function is used to parse the input
370     into a fixed-size buffer. Because of this, we must be prepared to
371     receive strings as long as the size of the input buffer, which is currently
372     set to 4K for Windows and 8K for the rest.
373     So, we must either allocate sufficiently large buffers here, or
374     reduce the size of the input buffer in the input reading part.
375 */
376
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
379 char thinkOutput1[MSG_SIZ*10];
380
381 ChessProgramState first, second;
382
383 /* premove variables */
384 int premoveToX = 0;
385 int premoveToY = 0;
386 int premoveFromX = 0;
387 int premoveFromY = 0;
388 int premovePromoChar = 0;
389 int gotPremove = 0;
390 Boolean alarmSounded;
391 /* end premove variables */
392
393 char *ics_prefix = "$";
394 int ics_type = ICS_GENERIC;
395
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
397 int pauseExamForwardMostMove = 0;
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
404 int whiteFlag = FALSE, blackFlag = FALSE;
405 int userOfferedDraw = FALSE;
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
408 int cmailMoveType[CMAIL_MAX_GAMES];
409 long ics_clock_paused = 0;
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
412 GameMode gameMode = BeginningOfGame;
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
416 int hiddenThinkOutputState = 0; /* [AS] */
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
418 int adjudicateLossPlies = 6;
419 char white_holding[64], black_holding[64];
420 TimeMark lastNodeCountTime;
421 long lastNodeCount=0;
422 int have_sent_ICS_logon = 0;
423 int movesPerSession;
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
425 long timeControl_2; /* [AS] Allow separate time controls */
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
427 long timeRemaining[2][MAX_MOVES];
428 int matchGame = 0;
429 TimeMark programStartTime;
430 char ics_handle[MSG_SIZ];
431 int have_set_title = 0;
432
433 /* animateTraining preserves the state of appData.animate
434  * when Training mode is activated. This allows the
435  * response to be animated when appData.animate == TRUE and
436  * appData.animateDragging == TRUE.
437  */
438 Boolean animateTraining;
439
440 GameInfo gameInfo;
441
442 AppData appData;
443
444 Board boards[MAX_MOVES];
445 /* [HGM] Following 7 needed for accurate legality tests: */
446 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
447 signed char  initialRights[BOARD_FILES];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
451 int loadFlag = 0; 
452 int shuffleOpenings;
453 int mute; // mute all sounds
454
455 // [HGM] vari: next 12 to save and restore variations
456 #define MAX_VARIATIONS 10
457 int framePtr = MAX_MOVES-1; // points to free stack entry
458 int storedGames = 0;
459 int savedFirst[MAX_VARIATIONS];
460 int savedLast[MAX_VARIATIONS];
461 int savedFramePtr[MAX_VARIATIONS];
462 char *savedDetails[MAX_VARIATIONS];
463 ChessMove savedResult[MAX_VARIATIONS];
464
465 void PushTail P((int firstMove, int lastMove));
466 Boolean PopTail P((Boolean annotate));
467 void CleanupTail P((void));
468
469 ChessSquare  FIDEArray[2][BOARD_FILES] = {
470     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
471         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
472     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
473         BlackKing, BlackBishop, BlackKnight, BlackRook }
474 };
475
476 ChessSquare twoKingsArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackKing, BlackKnight, BlackRook }
481 };
482
483 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
485         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
486     { BlackRook, BlackMan, BlackBishop, BlackQueen,
487         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
488 };
489
490 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
491     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
492         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
493     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
494         BlackKing, BlackBishop, BlackKnight, BlackRook }
495 };
496
497 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
498     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
499         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
500     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
501         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
502 };
503
504
505 #if (BOARD_FILES>=10)
506 ChessSquare ShogiArray[2][BOARD_FILES] = {
507     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
508         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
509     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
510         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
511 };
512
513 ChessSquare XiangqiArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
515         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
517         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
518 };
519
520 ChessSquare CapablancaArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
522         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
524         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
525 };
526
527 ChessSquare GreatArray[2][BOARD_FILES] = {
528     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
529         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
530     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
531         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
532 };
533
534 ChessSquare JanusArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
536         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
537     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
538         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
539 };
540
541 #ifdef GOTHIC
542 ChessSquare GothicArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
544         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
546         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
547 };
548 #else // !GOTHIC
549 #define GothicArray CapablancaArray
550 #endif // !GOTHIC
551
552 #ifdef FALCON
553 ChessSquare FalconArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
555         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
557         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
558 };
559 #else // !FALCON
560 #define FalconArray CapablancaArray
561 #endif // !FALCON
562
563 #else // !(BOARD_FILES>=10)
564 #define XiangqiPosition FIDEArray
565 #define CapablancaArray FIDEArray
566 #define GothicArray FIDEArray
567 #define GreatArray FIDEArray
568 #endif // !(BOARD_FILES>=10)
569
570 #if (BOARD_FILES>=12)
571 ChessSquare CourierArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
573         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
575         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
576 };
577 #else // !(BOARD_FILES>=12)
578 #define CourierArray CapablancaArray
579 #endif // !(BOARD_FILES>=12)
580
581
582 Board initialPosition;
583
584
585 /* Convert str to a rating. Checks for special cases of "----",
586
587    "++++", etc. Also strips ()'s */
588 int
589 string_to_rating(str)
590   char *str;
591 {
592   while(*str && !isdigit(*str)) ++str;
593   if (!*str)
594     return 0;   /* One of the special "no rating" cases */
595   else
596     return atoi(str);
597 }
598
599 void
600 ClearProgramStats()
601 {
602     /* Init programStats */
603     programStats.movelist[0] = 0;
604     programStats.depth = 0;
605     programStats.nr_moves = 0;
606     programStats.moves_left = 0;
607     programStats.nodes = 0;
608     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
609     programStats.score = 0;
610     programStats.got_only_move = 0;
611     programStats.got_fail = 0;
612     programStats.line_is_book = 0;
613 }
614
615 void
616 InitBackEnd1()
617 {
618     int matched, min, sec;
619
620     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
621
622     GetTimeMark(&programStartTime);
623     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
624
625     ClearProgramStats();
626     programStats.ok_to_send = 1;
627     programStats.seen_stat = 0;
628
629     /*
630      * Initialize game list
631      */
632     ListNew(&gameList);
633
634
635     /*
636      * Internet chess server status
637      */
638     if (appData.icsActive) {
639         appData.matchMode = FALSE;
640         appData.matchGames = 0;
641 #if ZIPPY       
642         appData.noChessProgram = !appData.zippyPlay;
643 #else
644         appData.zippyPlay = FALSE;
645         appData.zippyTalk = FALSE;
646         appData.noChessProgram = TRUE;
647 #endif
648         if (*appData.icsHelper != NULLCHAR) {
649             appData.useTelnet = TRUE;
650             appData.telnetProgram = appData.icsHelper;
651         }
652     } else {
653         appData.zippyTalk = appData.zippyPlay = FALSE;
654     }
655
656     /* [AS] Initialize pv info list [HGM] and game state */
657     {
658         int i, j;
659
660         for( i=0; i<=framePtr; i++ ) {
661             pvInfoList[i].depth = -1;
662             boards[i][EP_STATUS] = EP_NONE;
663             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
664         }
665     }
666
667     /*
668      * Parse timeControl resource
669      */
670     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
671                           appData.movesPerSession)) {
672         char buf[MSG_SIZ];
673         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
674         DisplayFatalError(buf, 0, 2);
675     }
676
677     /*
678      * Parse searchTime resource
679      */
680     if (*appData.searchTime != NULLCHAR) {
681         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
682         if (matched == 1) {
683             searchTime = min * 60;
684         } else if (matched == 2) {
685             searchTime = min * 60 + sec;
686         } else {
687             char buf[MSG_SIZ];
688             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
689             DisplayFatalError(buf, 0, 2);
690         }
691     }
692
693     /* [AS] Adjudication threshold */
694     adjudicateLossThreshold = appData.adjudicateLossThreshold;
695     
696     first.which = "first";
697     second.which = "second";
698     first.maybeThinking = second.maybeThinking = FALSE;
699     first.pr = second.pr = NoProc;
700     first.isr = second.isr = NULL;
701     first.sendTime = second.sendTime = 2;
702     first.sendDrawOffers = 1;
703     if (appData.firstPlaysBlack) {
704         first.twoMachinesColor = "black\n";
705         second.twoMachinesColor = "white\n";
706     } else {
707         first.twoMachinesColor = "white\n";
708         second.twoMachinesColor = "black\n";
709     }
710     first.program = appData.firstChessProgram;
711     second.program = appData.secondChessProgram;
712     first.host = appData.firstHost;
713     second.host = appData.secondHost;
714     first.dir = appData.firstDirectory;
715     second.dir = appData.secondDirectory;
716     first.other = &second;
717     second.other = &first;
718     first.initString = appData.initString;
719     second.initString = appData.secondInitString;
720     first.computerString = appData.firstComputerString;
721     second.computerString = appData.secondComputerString;
722     first.useSigint = second.useSigint = TRUE;
723     first.useSigterm = second.useSigterm = TRUE;
724     first.reuse = appData.reuseFirst;
725     second.reuse = appData.reuseSecond;
726     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
727     second.nps = appData.secondNPS;
728     first.useSetboard = second.useSetboard = FALSE;
729     first.useSAN = second.useSAN = FALSE;
730     first.usePing = second.usePing = FALSE;
731     first.lastPing = second.lastPing = 0;
732     first.lastPong = second.lastPong = 0;
733     first.usePlayother = second.usePlayother = FALSE;
734     first.useColors = second.useColors = TRUE;
735     first.useUsermove = second.useUsermove = FALSE;
736     first.sendICS = second.sendICS = FALSE;
737     first.sendName = second.sendName = appData.icsActive;
738     first.sdKludge = second.sdKludge = FALSE;
739     first.stKludge = second.stKludge = FALSE;
740     TidyProgramName(first.program, first.host, first.tidy);
741     TidyProgramName(second.program, second.host, second.tidy);
742     first.matchWins = second.matchWins = 0;
743     strcpy(first.variants, appData.variant);
744     strcpy(second.variants, appData.variant);
745     first.analysisSupport = second.analysisSupport = 2; /* detect */
746     first.analyzing = second.analyzing = FALSE;
747     first.initDone = second.initDone = FALSE;
748
749     /* New features added by Tord: */
750     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
751     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
752     /* End of new features added by Tord. */
753     first.fenOverride  = appData.fenOverride1;
754     second.fenOverride = appData.fenOverride2;
755
756     /* [HGM] time odds: set factor for each machine */
757     first.timeOdds  = appData.firstTimeOdds;
758     second.timeOdds = appData.secondTimeOdds;
759     { int norm = 1;
760         if(appData.timeOddsMode) {
761             norm = first.timeOdds;
762             if(norm > second.timeOdds) norm = second.timeOdds;
763         }
764         first.timeOdds /= norm;
765         second.timeOdds /= norm;
766     }
767
768     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
769     first.accumulateTC = appData.firstAccumulateTC;
770     second.accumulateTC = appData.secondAccumulateTC;
771     first.maxNrOfSessions = second.maxNrOfSessions = 1;
772
773     /* [HGM] debug */
774     first.debug = second.debug = FALSE;
775     first.supportsNPS = second.supportsNPS = UNKNOWN;
776
777     /* [HGM] options */
778     first.optionSettings  = appData.firstOptions;
779     second.optionSettings = appData.secondOptions;
780
781     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
782     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
783     first.isUCI = appData.firstIsUCI; /* [AS] */
784     second.isUCI = appData.secondIsUCI; /* [AS] */
785     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
786     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
787
788     if (appData.firstProtocolVersion > PROTOVER ||
789         appData.firstProtocolVersion < 1) {
790       char buf[MSG_SIZ];
791       sprintf(buf, _("protocol version %d not supported"),
792               appData.firstProtocolVersion);
793       DisplayFatalError(buf, 0, 2);
794     } else {
795       first.protocolVersion = appData.firstProtocolVersion;
796     }
797
798     if (appData.secondProtocolVersion > PROTOVER ||
799         appData.secondProtocolVersion < 1) {
800       char buf[MSG_SIZ];
801       sprintf(buf, _("protocol version %d not supported"),
802               appData.secondProtocolVersion);
803       DisplayFatalError(buf, 0, 2);
804     } else {
805       second.protocolVersion = appData.secondProtocolVersion;
806     }
807
808     if (appData.icsActive) {
809         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
810 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
811     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
812         appData.clockMode = FALSE;
813         first.sendTime = second.sendTime = 0;
814     }
815     
816 #if ZIPPY
817     /* Override some settings from environment variables, for backward
818        compatibility.  Unfortunately it's not feasible to have the env
819        vars just set defaults, at least in xboard.  Ugh.
820     */
821     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822       ZippyInit();
823     }
824 #endif
825     
826     if (appData.noChessProgram) {
827         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828         sprintf(programVersion, "%s", PACKAGE_STRING);
829     } else {
830       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
831       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
832       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
833     }
834
835     if (!appData.icsActive) {
836       char buf[MSG_SIZ];
837       /* Check for variants that are supported only in ICS mode,
838          or not at all.  Some that are accepted here nevertheless
839          have bugs; see comments below.
840       */
841       VariantClass variant = StringToVariant(appData.variant);
842       switch (variant) {
843       case VariantBughouse:     /* need four players and two boards */
844       case VariantKriegspiel:   /* need to hide pieces and move details */
845       /* case VariantFischeRandom: (Fabien: moved below) */
846         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
847         DisplayFatalError(buf, 0, 2);
848         return;
849
850       case VariantUnknown:
851       case VariantLoadable:
852       case Variant29:
853       case Variant30:
854       case Variant31:
855       case Variant32:
856       case Variant33:
857       case Variant34:
858       case Variant35:
859       case Variant36:
860       default:
861         sprintf(buf, _("Unknown variant name %s"), appData.variant);
862         DisplayFatalError(buf, 0, 2);
863         return;
864
865       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
866       case VariantFairy:      /* [HGM] TestLegality definitely off! */
867       case VariantGothic:     /* [HGM] should work */
868       case VariantCapablanca: /* [HGM] should work */
869       case VariantCourier:    /* [HGM] initial forced moves not implemented */
870       case VariantShogi:      /* [HGM] drops not tested for legality */
871       case VariantKnightmate: /* [HGM] should work */
872       case VariantCylinder:   /* [HGM] untested */
873       case VariantFalcon:     /* [HGM] untested */
874       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
875                                  offboard interposition not understood */
876       case VariantNormal:     /* definitely works! */
877       case VariantWildCastle: /* pieces not automatically shuffled */
878       case VariantNoCastle:   /* pieces not automatically shuffled */
879       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
880       case VariantLosers:     /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantSuicide:    /* should work except for win condition,
883                                  and doesn't know captures are mandatory */
884       case VariantGiveaway:   /* should work except for win condition,
885                                  and doesn't know captures are mandatory */
886       case VariantTwoKings:   /* should work */
887       case VariantAtomic:     /* should work except for win condition */
888       case Variant3Check:     /* should work except for win condition */
889       case VariantShatranj:   /* should work except for all win conditions */
890       case VariantBerolina:   /* might work if TestLegality is off */
891       case VariantCapaRandom: /* should work */
892       case VariantJanus:      /* should work */
893       case VariantSuper:      /* experimental */
894       case VariantGreat:      /* experimental, requires legality testing to be off */
895         break;
896       }
897     }
898
899     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
900     InitEngineUCI( installDir, &second );
901 }
902
903 int NextIntegerFromString( char ** str, long * value )
904 {
905     int result = -1;
906     char * s = *str;
907
908     while( *s == ' ' || *s == '\t' ) {
909         s++;
910     }
911
912     *value = 0;
913
914     if( *s >= '0' && *s <= '9' ) {
915         while( *s >= '0' && *s <= '9' ) {
916             *value = *value * 10 + (*s - '0');
917             s++;
918         }
919
920         result = 0;
921     }
922
923     *str = s;
924
925     return result;
926 }
927
928 int NextTimeControlFromString( char ** str, long * value )
929 {
930     long temp;
931     int result = NextIntegerFromString( str, &temp );
932
933     if( result == 0 ) {
934         *value = temp * 60; /* Minutes */
935         if( **str == ':' ) {
936             (*str)++;
937             result = NextIntegerFromString( str, &temp );
938             *value += temp; /* Seconds */
939         }
940     }
941
942     return result;
943 }
944
945 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
946 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
947     int result = -1; long temp, temp2;
948
949     if(**str != '+') return -1; // old params remain in force!
950     (*str)++;
951     if( NextTimeControlFromString( str, &temp ) ) return -1;
952
953     if(**str != '/') {
954         /* time only: incremental or sudden-death time control */
955         if(**str == '+') { /* increment follows; read it */
956             (*str)++;
957             if(result = NextIntegerFromString( str, &temp2)) return -1;
958             *inc = temp2 * 1000;
959         } else *inc = 0;
960         *moves = 0; *tc = temp * 1000; 
961         return 0;
962     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
963
964     (*str)++; /* classical time control */
965     result = NextTimeControlFromString( str, &temp2);
966     if(result == 0) {
967         *moves = temp/60;
968         *tc    = temp2 * 1000;
969         *inc   = 0;
970     }
971     return result;
972 }
973
974 int GetTimeQuota(int movenr)
975 {   /* [HGM] get time to add from the multi-session time-control string */
976     int moves=1; /* kludge to force reading of first session */
977     long time, increment;
978     char *s = fullTimeControlString;
979
980     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
981     do {
982         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
983         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
984         if(movenr == -1) return time;    /* last move before new session     */
985         if(!moves) return increment;     /* current session is incremental   */
986         if(movenr >= 0) movenr -= moves; /* we already finished this session */
987     } while(movenr >= -1);               /* try again for next session       */
988
989     return 0; // no new time quota on this move
990 }
991
992 int
993 ParseTimeControl(tc, ti, mps)
994      char *tc;
995      int ti;
996      int mps;
997 {
998   long tc1;
999   long tc2;
1000   char buf[MSG_SIZ];
1001   
1002   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1003   if(ti > 0) {
1004     if(mps)
1005       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1006     else sprintf(buf, "+%s+%d", tc, ti);
1007   } else {
1008     if(mps)
1009              sprintf(buf, "+%d/%s", mps, tc);
1010     else sprintf(buf, "+%s", tc);
1011   }
1012   fullTimeControlString = StrSave(buf);
1013   
1014   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1015     return FALSE;
1016   }
1017   
1018   if( *tc == '/' ) {
1019     /* Parse second time control */
1020     tc++;
1021     
1022     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1023       return FALSE;
1024     }
1025     
1026     if( tc2 == 0 ) {
1027       return FALSE;
1028     }
1029     
1030     timeControl_2 = tc2 * 1000;
1031   }
1032   else {
1033     timeControl_2 = 0;
1034   }
1035   
1036   if( tc1 == 0 ) {
1037     return FALSE;
1038   }
1039   
1040   timeControl = tc1 * 1000;
1041   
1042   if (ti >= 0) {
1043     timeIncrement = ti * 1000;  /* convert to ms */
1044     movesPerSession = 0;
1045   } else {
1046     timeIncrement = 0;
1047     movesPerSession = mps;
1048   }
1049   return TRUE;
1050 }
1051
1052 void
1053 InitBackEnd2()
1054 {
1055     if (appData.debugMode) {
1056         fprintf(debugFP, "%s\n", programVersion);
1057     }
1058
1059     set_cont_sequence(appData.wrapContSeq);
1060     if (appData.matchGames > 0) {
1061         appData.matchMode = TRUE;
1062     } else if (appData.matchMode) {
1063         appData.matchGames = 1;
1064     }
1065     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1066         appData.matchGames = appData.sameColorGames;
1067     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1068         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1069         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1070     }
1071     Reset(TRUE, FALSE);
1072     if (appData.noChessProgram || first.protocolVersion == 1) {
1073       InitBackEnd3();
1074     } else {
1075       /* kludge: allow timeout for initial "feature" commands */
1076       FreezeUI();
1077       DisplayMessage("", _("Starting chess program"));
1078       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1079     }
1080 }
1081
1082 void
1083 InitBackEnd3 P((void))
1084 {
1085     GameMode initialMode;
1086     char buf[MSG_SIZ];
1087     int err;
1088
1089     InitChessProgram(&first, startedFromSetupPosition);
1090
1091
1092     if (appData.icsActive) {
1093 #ifdef WIN32
1094         /* [DM] Make a console window if needed [HGM] merged ifs */
1095         ConsoleCreate(); 
1096 #endif
1097         err = establish();
1098         if (err != 0) {
1099             if (*appData.icsCommPort != NULLCHAR) {
1100                 sprintf(buf, _("Could not open comm port %s"),  
1101                         appData.icsCommPort);
1102             } else {
1103                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1104                         appData.icsHost, appData.icsPort);
1105             }
1106             DisplayFatalError(buf, err, 1);
1107             return;
1108         }
1109         SetICSMode();
1110         telnetISR =
1111           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1112         fromUserISR =
1113           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1114     } else if (appData.noChessProgram) {
1115         SetNCPMode();
1116     } else {
1117         SetGNUMode();
1118     }
1119
1120     if (*appData.cmailGameName != NULLCHAR) {
1121         SetCmailMode();
1122         OpenLoopback(&cmailPR);
1123         cmailISR =
1124           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1125     }
1126     
1127     ThawUI();
1128     DisplayMessage("", "");
1129     if (StrCaseCmp(appData.initialMode, "") == 0) {
1130       initialMode = BeginningOfGame;
1131     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1132       initialMode = TwoMachinesPlay;
1133     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1134       initialMode = AnalyzeFile; 
1135     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1136       initialMode = AnalyzeMode;
1137     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1138       initialMode = MachinePlaysWhite;
1139     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1140       initialMode = MachinePlaysBlack;
1141     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1142       initialMode = EditGame;
1143     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1144       initialMode = EditPosition;
1145     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1146       initialMode = Training;
1147     } else {
1148       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1149       DisplayFatalError(buf, 0, 2);
1150       return;
1151     }
1152
1153     if (appData.matchMode) {
1154         /* Set up machine vs. machine match */
1155         if (appData.noChessProgram) {
1156             DisplayFatalError(_("Can't have a match with no chess programs"),
1157                               0, 2);
1158             return;
1159         }
1160         matchMode = TRUE;
1161         matchGame = 1;
1162         if (*appData.loadGameFile != NULLCHAR) {
1163             int index = appData.loadGameIndex; // [HGM] autoinc
1164             if(index<0) lastIndex = index = 1;
1165             if (!LoadGameFromFile(appData.loadGameFile,
1166                                   index,
1167                                   appData.loadGameFile, FALSE)) {
1168                 DisplayFatalError(_("Bad game file"), 0, 1);
1169                 return;
1170             }
1171         } else if (*appData.loadPositionFile != NULLCHAR) {
1172             int index = appData.loadPositionIndex; // [HGM] autoinc
1173             if(index<0) lastIndex = index = 1;
1174             if (!LoadPositionFromFile(appData.loadPositionFile,
1175                                       index,
1176                                       appData.loadPositionFile)) {
1177                 DisplayFatalError(_("Bad position file"), 0, 1);
1178                 return;
1179             }
1180         }
1181         TwoMachinesEvent();
1182     } else if (*appData.cmailGameName != NULLCHAR) {
1183         /* Set up cmail mode */
1184         ReloadCmailMsgEvent(TRUE);
1185     } else {
1186         /* Set up other modes */
1187         if (initialMode == AnalyzeFile) {
1188           if (*appData.loadGameFile == NULLCHAR) {
1189             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1190             return;
1191           }
1192         }
1193         if (*appData.loadGameFile != NULLCHAR) {
1194             (void) LoadGameFromFile(appData.loadGameFile,
1195                                     appData.loadGameIndex,
1196                                     appData.loadGameFile, TRUE);
1197         } else if (*appData.loadPositionFile != NULLCHAR) {
1198             (void) LoadPositionFromFile(appData.loadPositionFile,
1199                                         appData.loadPositionIndex,
1200                                         appData.loadPositionFile);
1201             /* [HGM] try to make self-starting even after FEN load */
1202             /* to allow automatic setup of fairy variants with wtm */
1203             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1204                 gameMode = BeginningOfGame;
1205                 setboardSpoiledMachineBlack = 1;
1206             }
1207             /* [HGM] loadPos: make that every new game uses the setup */
1208             /* from file as long as we do not switch variant          */
1209             if(!blackPlaysFirst) {
1210                 startedFromPositionFile = TRUE;
1211                 CopyBoard(filePosition, boards[0]);
1212             }
1213         }
1214         if (initialMode == AnalyzeMode) {
1215           if (appData.noChessProgram) {
1216             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1221             return;
1222           }
1223           AnalyzeModeEvent();
1224         } else if (initialMode == AnalyzeFile) {
1225           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1226           ShowThinkingEvent();
1227           AnalyzeFileEvent();
1228           AnalysisPeriodicEvent(1);
1229         } else if (initialMode == MachinePlaysWhite) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1232                               0, 2);
1233             return;
1234           }
1235           if (appData.icsActive) {
1236             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1237                               0, 2);
1238             return;
1239           }
1240           MachineWhiteEvent();
1241         } else if (initialMode == MachinePlaysBlack) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1244                               0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1249                               0, 2);
1250             return;
1251           }
1252           MachineBlackEvent();
1253         } else if (initialMode == TwoMachinesPlay) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           TwoMachinesEvent();
1265         } else if (initialMode == EditGame) {
1266           EditGameEvent();
1267         } else if (initialMode == EditPosition) {
1268           EditPositionEvent();
1269         } else if (initialMode == Training) {
1270           if (*appData.loadGameFile == NULLCHAR) {
1271             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1272             return;
1273           }
1274           TrainingEvent();
1275         }
1276     }
1277 }
1278
1279 /*
1280  * Establish will establish a contact to a remote host.port.
1281  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1282  *  used to talk to the host.
1283  * Returns 0 if okay, error code if not.
1284  */
1285 int
1286 establish()
1287 {
1288     char buf[MSG_SIZ];
1289
1290     if (*appData.icsCommPort != NULLCHAR) {
1291         /* Talk to the host through a serial comm port */
1292         return OpenCommPort(appData.icsCommPort, &icsPR);
1293
1294     } else if (*appData.gateway != NULLCHAR) {
1295         if (*appData.remoteShell == NULLCHAR) {
1296             /* Use the rcmd protocol to run telnet program on a gateway host */
1297             snprintf(buf, sizeof(buf), "%s %s %s",
1298                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1299             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300
1301         } else {
1302             /* Use the rsh program to run telnet program on a gateway host */
1303             if (*appData.remoteUser == NULLCHAR) {
1304                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1305                         appData.gateway, appData.telnetProgram,
1306                         appData.icsHost, appData.icsPort);
1307             } else {
1308                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1309                         appData.remoteShell, appData.gateway, 
1310                         appData.remoteUser, appData.telnetProgram,
1311                         appData.icsHost, appData.icsPort);
1312             }
1313             return StartChildProcess(buf, "", &icsPR);
1314
1315         }
1316     } else if (appData.useTelnet) {
1317         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318
1319     } else {
1320         /* TCP socket interface differs somewhat between
1321            Unix and NT; handle details in the front end.
1322            */
1323         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1324     }
1325 }
1326
1327 void
1328 show_bytes(fp, buf, count)
1329      FILE *fp;
1330      char *buf;
1331      int count;
1332 {
1333     while (count--) {
1334         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1335             fprintf(fp, "\\%03o", *buf & 0xff);
1336         } else {
1337             putc(*buf, fp);
1338         }
1339         buf++;
1340     }
1341     fflush(fp);
1342 }
1343
1344 /* Returns an errno value */
1345 int
1346 OutputMaybeTelnet(pr, message, count, outError)
1347      ProcRef pr;
1348      char *message;
1349      int count;
1350      int *outError;
1351 {
1352     char buf[8192], *p, *q, *buflim;
1353     int left, newcount, outcount;
1354
1355     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1356         *appData.gateway != NULLCHAR) {
1357         if (appData.debugMode) {
1358             fprintf(debugFP, ">ICS: ");
1359             show_bytes(debugFP, message, count);
1360             fprintf(debugFP, "\n");
1361         }
1362         return OutputToProcess(pr, message, count, outError);
1363     }
1364
1365     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1366     p = message;
1367     q = buf;
1368     left = count;
1369     newcount = 0;
1370     while (left) {
1371         if (q >= buflim) {
1372             if (appData.debugMode) {
1373                 fprintf(debugFP, ">ICS: ");
1374                 show_bytes(debugFP, buf, newcount);
1375                 fprintf(debugFP, "\n");
1376             }
1377             outcount = OutputToProcess(pr, buf, newcount, outError);
1378             if (outcount < newcount) return -1; /* to be sure */
1379             q = buf;
1380             newcount = 0;
1381         }
1382         if (*p == '\n') {
1383             *q++ = '\r';
1384             newcount++;
1385         } else if (((unsigned char) *p) == TN_IAC) {
1386             *q++ = (char) TN_IAC;
1387             newcount ++;
1388         }
1389         *q++ = *p++;
1390         newcount++;
1391         left--;
1392     }
1393     if (appData.debugMode) {
1394         fprintf(debugFP, ">ICS: ");
1395         show_bytes(debugFP, buf, newcount);
1396         fprintf(debugFP, "\n");
1397     }
1398     outcount = OutputToProcess(pr, buf, newcount, outError);
1399     if (outcount < newcount) return -1; /* to be sure */
1400     return count;
1401 }
1402
1403 void
1404 read_from_player(isr, closure, message, count, error)
1405      InputSourceRef isr;
1406      VOIDSTAR closure;
1407      char *message;
1408      int count;
1409      int error;
1410 {
1411     int outError, outCount;
1412     static int gotEof = 0;
1413
1414     /* Pass data read from player on to ICS */
1415     if (count > 0) {
1416         gotEof = 0;
1417         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1418         if (outCount < count) {
1419             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1420         }
1421     } else if (count < 0) {
1422         RemoveInputSource(isr);
1423         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1424     } else if (gotEof++ > 0) {
1425         RemoveInputSource(isr);
1426         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1427     }
1428 }
1429
1430 void
1431 KeepAlive()
1432 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1433     SendToICS("date\n");
1434     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435 }
1436
1437 /* added routine for printf style output to ics */
1438 void ics_printf(char *format, ...)
1439 {
1440     char buffer[MSG_SIZ];
1441     va_list args;
1442
1443     va_start(args, format);
1444     vsnprintf(buffer, sizeof(buffer), format, args);
1445     buffer[sizeof(buffer)-1] = '\0';
1446     SendToICS(buffer);
1447     va_end(args);
1448 }
1449
1450 void
1451 SendToICS(s)
1452      char *s;
1453 {
1454     int count, outCount, outError;
1455
1456     if (icsPR == NULL) return;
1457
1458     count = strlen(s);
1459     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1460     if (outCount < count) {
1461         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1462     }
1463 }
1464
1465 /* This is used for sending logon scripts to the ICS. Sending
1466    without a delay causes problems when using timestamp on ICC
1467    (at least on my machine). */
1468 void
1469 SendToICSDelayed(s,msdelay)
1470      char *s;
1471      long msdelay;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     if (appData.debugMode) {
1479         fprintf(debugFP, ">ICS: ");
1480         show_bytes(debugFP, s, count);
1481         fprintf(debugFP, "\n");
1482     }
1483     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1484                                       msdelay);
1485     if (outCount < count) {
1486         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1487     }
1488 }
1489
1490
1491 /* Remove all highlighting escape sequences in s
1492    Also deletes any suffix starting with '(' 
1493    */
1494 char *
1495 StripHighlightAndTitle(s)
1496      char *s;
1497 {
1498     static char retbuf[MSG_SIZ];
1499     char *p = retbuf;
1500
1501     while (*s != NULLCHAR) {
1502         while (*s == '\033') {
1503             while (*s != NULLCHAR && !isalpha(*s)) s++;
1504             if (*s != NULLCHAR) s++;
1505         }
1506         while (*s != NULLCHAR && *s != '\033') {
1507             if (*s == '(' || *s == '[') {
1508                 *p = NULLCHAR;
1509                 return retbuf;
1510             }
1511             *p++ = *s++;
1512         }
1513     }
1514     *p = NULLCHAR;
1515     return retbuf;
1516 }
1517
1518 /* Remove all highlighting escape sequences in s */
1519 char *
1520 StripHighlight(s)
1521      char *s;
1522 {
1523     static char retbuf[MSG_SIZ];
1524     char *p = retbuf;
1525
1526     while (*s != NULLCHAR) {
1527         while (*s == '\033') {
1528             while (*s != NULLCHAR && !isalpha(*s)) s++;
1529             if (*s != NULLCHAR) s++;
1530         }
1531         while (*s != NULLCHAR && *s != '\033') {
1532             *p++ = *s++;
1533         }
1534     }
1535     *p = NULLCHAR;
1536     return retbuf;
1537 }
1538
1539 char *variantNames[] = VARIANT_NAMES;
1540 char *
1541 VariantName(v)
1542      VariantClass v;
1543 {
1544     return variantNames[v];
1545 }
1546
1547
1548 /* Identify a variant from the strings the chess servers use or the
1549    PGN Variant tag names we use. */
1550 VariantClass
1551 StringToVariant(e)
1552      char *e;
1553 {
1554     char *p;
1555     int wnum = -1;
1556     VariantClass v = VariantNormal;
1557     int i, found = FALSE;
1558     char buf[MSG_SIZ];
1559
1560     if (!e) return v;
1561
1562     /* [HGM] skip over optional board-size prefixes */
1563     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1564         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1565         while( *e++ != '_');
1566     }
1567
1568     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1569         v = VariantNormal;
1570         found = TRUE;
1571     } else
1572     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573       if (StrCaseStr(e, variantNames[i])) {
1574         v = (VariantClass) i;
1575         found = TRUE;
1576         break;
1577       }
1578     }
1579
1580     if (!found) {
1581       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582           || StrCaseStr(e, "wild/fr") 
1583           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584         v = VariantFischeRandom;
1585       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586                  (i = 1, p = StrCaseStr(e, "w"))) {
1587         p += i;
1588         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1589         if (isdigit(*p)) {
1590           wnum = atoi(p);
1591         } else {
1592           wnum = -1;
1593         }
1594         switch (wnum) {
1595         case 0: /* FICS only, actually */
1596         case 1:
1597           /* Castling legal even if K starts on d-file */
1598           v = VariantWildCastle;
1599           break;
1600         case 2:
1601         case 3:
1602         case 4:
1603           /* Castling illegal even if K & R happen to start in
1604              normal positions. */
1605           v = VariantNoCastle;
1606           break;
1607         case 5:
1608         case 7:
1609         case 8:
1610         case 10:
1611         case 11:
1612         case 12:
1613         case 13:
1614         case 14:
1615         case 15:
1616         case 18:
1617         case 19:
1618           /* Castling legal iff K & R start in normal positions */
1619           v = VariantNormal;
1620           break;
1621         case 6:
1622         case 20:
1623         case 21:
1624           /* Special wilds for position setup; unclear what to do here */
1625           v = VariantLoadable;
1626           break;
1627         case 9:
1628           /* Bizarre ICC game */
1629           v = VariantTwoKings;
1630           break;
1631         case 16:
1632           v = VariantKriegspiel;
1633           break;
1634         case 17:
1635           v = VariantLosers;
1636           break;
1637         case 22:
1638           v = VariantFischeRandom;
1639           break;
1640         case 23:
1641           v = VariantCrazyhouse;
1642           break;
1643         case 24:
1644           v = VariantBughouse;
1645           break;
1646         case 25:
1647           v = Variant3Check;
1648           break;
1649         case 26:
1650           /* Not quite the same as FICS suicide! */
1651           v = VariantGiveaway;
1652           break;
1653         case 27:
1654           v = VariantAtomic;
1655           break;
1656         case 28:
1657           v = VariantShatranj;
1658           break;
1659
1660         /* Temporary names for future ICC types.  The name *will* change in 
1661            the next xboard/WinBoard release after ICC defines it. */
1662         case 29:
1663           v = Variant29;
1664           break;
1665         case 30:
1666           v = Variant30;
1667           break;
1668         case 31:
1669           v = Variant31;
1670           break;
1671         case 32:
1672           v = Variant32;
1673           break;
1674         case 33:
1675           v = Variant33;
1676           break;
1677         case 34:
1678           v = Variant34;
1679           break;
1680         case 35:
1681           v = Variant35;
1682           break;
1683         case 36:
1684           v = Variant36;
1685           break;
1686         case 37:
1687           v = VariantShogi;
1688           break;
1689         case 38:
1690           v = VariantXiangqi;
1691           break;
1692         case 39:
1693           v = VariantCourier;
1694           break;
1695         case 40:
1696           v = VariantGothic;
1697           break;
1698         case 41:
1699           v = VariantCapablanca;
1700           break;
1701         case 42:
1702           v = VariantKnightmate;
1703           break;
1704         case 43:
1705           v = VariantFairy;
1706           break;
1707         case 44:
1708           v = VariantCylinder;
1709           break;
1710         case 45:
1711           v = VariantFalcon;
1712           break;
1713         case 46:
1714           v = VariantCapaRandom;
1715           break;
1716         case 47:
1717           v = VariantBerolina;
1718           break;
1719         case 48:
1720           v = VariantJanus;
1721           break;
1722         case 49:
1723           v = VariantSuper;
1724           break;
1725         case 50:
1726           v = VariantGreat;
1727           break;
1728         case -1:
1729           /* Found "wild" or "w" in the string but no number;
1730              must assume it's normal chess. */
1731           v = VariantNormal;
1732           break;
1733         default:
1734           sprintf(buf, _("Unknown wild type %d"), wnum);
1735           DisplayError(buf, 0);
1736           v = VariantUnknown;
1737           break;
1738         }
1739       }
1740     }
1741     if (appData.debugMode) {
1742       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743               e, wnum, VariantName(v));
1744     }
1745     return v;
1746 }
1747
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1750
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752    advance *index beyond it, and set leftover_start to the new value of
1753    *index; else return FALSE.  If pattern contains the character '*', it
1754    matches any sequence of characters not containing '\r', '\n', or the
1755    character following the '*' (if any), and the matched sequence(s) are
1756    copied into star_match.
1757    */
1758 int
1759 looking_at(buf, index, pattern)
1760      char *buf;
1761      int *index;
1762      char *pattern;
1763 {
1764     char *bufp = &buf[*index], *patternp = pattern;
1765     int star_count = 0;
1766     char *matchp = star_match[0];
1767     
1768     for (;;) {
1769         if (*patternp == NULLCHAR) {
1770             *index = leftover_start = bufp - buf;
1771             *matchp = NULLCHAR;
1772             return TRUE;
1773         }
1774         if (*bufp == NULLCHAR) return FALSE;
1775         if (*patternp == '*') {
1776             if (*bufp == *(patternp + 1)) {
1777                 *matchp = NULLCHAR;
1778                 matchp = star_match[++star_count];
1779                 patternp += 2;
1780                 bufp++;
1781                 continue;
1782             } else if (*bufp == '\n' || *bufp == '\r') {
1783                 patternp++;
1784                 if (*patternp == NULLCHAR)
1785                   continue;
1786                 else
1787                   return FALSE;
1788             } else {
1789                 *matchp++ = *bufp++;
1790                 continue;
1791             }
1792         }
1793         if (*patternp != *bufp) return FALSE;
1794         patternp++;
1795         bufp++;
1796     }
1797 }
1798
1799 void
1800 SendToPlayer(data, length)
1801      char *data;
1802      int length;
1803 {
1804     int error, outCount;
1805     outCount = OutputToProcess(NoProc, data, length, &error);
1806     if (outCount < length) {
1807         DisplayFatalError(_("Error writing to display"), error, 1);
1808     }
1809 }
1810
1811 void
1812 PackHolding(packed, holding)
1813      char packed[];
1814      char *holding;
1815 {
1816     char *p = holding;
1817     char *q = packed;
1818     int runlength = 0;
1819     int curr = 9999;
1820     do {
1821         if (*p == curr) {
1822             runlength++;
1823         } else {
1824             switch (runlength) {
1825               case 0:
1826                 break;
1827               case 1:
1828                 *q++ = curr;
1829                 break;
1830               case 2:
1831                 *q++ = curr;
1832                 *q++ = curr;
1833                 break;
1834               default:
1835                 sprintf(q, "%d", runlength);
1836                 while (*q) q++;
1837                 *q++ = curr;
1838                 break;
1839             }
1840             runlength = 1;
1841             curr = *p;
1842         }
1843     } while (*p++);
1844     *q = NULLCHAR;
1845 }
1846
1847 /* Telnet protocol requests from the front end */
1848 void
1849 TelnetRequest(ddww, option)
1850      unsigned char ddww, option;
1851 {
1852     unsigned char msg[3];
1853     int outCount, outError;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1856
1857     if (appData.debugMode) {
1858         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1859         switch (ddww) {
1860           case TN_DO:
1861             ddwwStr = "DO";
1862             break;
1863           case TN_DONT:
1864             ddwwStr = "DONT";
1865             break;
1866           case TN_WILL:
1867             ddwwStr = "WILL";
1868             break;
1869           case TN_WONT:
1870             ddwwStr = "WONT";
1871             break;
1872           default:
1873             ddwwStr = buf1;
1874             sprintf(buf1, "%d", ddww);
1875             break;
1876         }
1877         switch (option) {
1878           case TN_ECHO:
1879             optionStr = "ECHO";
1880             break;
1881           default:
1882             optionStr = buf2;
1883             sprintf(buf2, "%d", option);
1884             break;
1885         }
1886         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1887     }
1888     msg[0] = TN_IAC;
1889     msg[1] = ddww;
1890     msg[2] = option;
1891     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1892     if (outCount < 3) {
1893         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894     }
1895 }
1896
1897 void
1898 DoEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DO, TN_ECHO);
1902 }
1903
1904 void
1905 DontEcho()
1906 {
1907     if (!appData.icsActive) return;
1908     TelnetRequest(TN_DONT, TN_ECHO);
1909 }
1910
1911 void
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1913 {
1914     /* put the holdings sent to us by the server on the board holdings area */
1915     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916     char p;
1917     ChessSquare piece;
1918
1919     if(gameInfo.holdingsWidth < 2)  return;
1920     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1921         return; // prevent overwriting by pre-board holdings
1922
1923     if( (int)lowestPiece >= BlackPawn ) {
1924         holdingsColumn = 0;
1925         countsColumn = 1;
1926         holdingsStartRow = BOARD_HEIGHT-1;
1927         direction = -1;
1928     } else {
1929         holdingsColumn = BOARD_WIDTH-1;
1930         countsColumn = BOARD_WIDTH-2;
1931         holdingsStartRow = 0;
1932         direction = 1;
1933     }
1934
1935     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1936         board[i][holdingsColumn] = EmptySquare;
1937         board[i][countsColumn]   = (ChessSquare) 0;
1938     }
1939     while( (p=*holdings++) != NULLCHAR ) {
1940         piece = CharToPiece( ToUpper(p) );
1941         if(piece == EmptySquare) continue;
1942         /*j = (int) piece - (int) WhitePawn;*/
1943         j = PieceToNumber(piece);
1944         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1945         if(j < 0) continue;               /* should not happen */
1946         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1947         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1948         board[holdingsStartRow+j*direction][countsColumn]++;
1949     }
1950 }
1951
1952
1953 void
1954 VariantSwitch(Board board, VariantClass newVariant)
1955 {
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957    Board oldBoard;
1958
1959    startedFromPositionFile = FALSE;
1960    if(gameInfo.variant == newVariant) return;
1961
1962    /* [HGM] This routine is called each time an assignment is made to
1963     * gameInfo.variant during a game, to make sure the board sizes
1964     * are set to match the new variant. If that means adding or deleting
1965     * holdings, we shift the playing board accordingly
1966     * This kludge is needed because in ICS observe mode, we get boards
1967     * of an ongoing game without knowing the variant, and learn about the
1968     * latter only later. This can be because of the move list we requested,
1969     * in which case the game history is refilled from the beginning anyway,
1970     * but also when receiving holdings of a crazyhouse game. In the latter
1971     * case we want to add those holdings to the already received position.
1972     */
1973
1974    
1975    if (appData.debugMode) {
1976      fprintf(debugFP, "Switch board from %s to %s\n",
1977              VariantName(gameInfo.variant), VariantName(newVariant));
1978      setbuf(debugFP, NULL);
1979    }
1980    shuffleOpenings = 0;       /* [HGM] shuffle */
1981    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982    switch(newVariant) 
1983      {
1984      case VariantShogi:
1985        newWidth = 9;  newHeight = 9;
1986        gameInfo.holdingsSize = 7;
1987      case VariantBughouse:
1988      case VariantCrazyhouse:
1989        newHoldingsWidth = 2; break;
1990      case VariantGreat:
1991        newWidth = 10;
1992      case VariantSuper:
1993        newHoldingsWidth = 2;
1994        gameInfo.holdingsSize = 8;
1995        break;
1996      case VariantGothic:
1997      case VariantCapablanca:
1998      case VariantCapaRandom:
1999        newWidth = 10;
2000      default:
2001        newHoldingsWidth = gameInfo.holdingsSize = 0;
2002      };
2003    
2004    if(newWidth  != gameInfo.boardWidth  ||
2005       newHeight != gameInfo.boardHeight ||
2006       newHoldingsWidth != gameInfo.holdingsWidth ) {
2007      
2008      /* shift position to new playing area, if needed */
2009      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010        for(i=0; i<BOARD_HEIGHT; i++) 
2011          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013              board[i][j];
2014        for(i=0; i<newHeight; i++) {
2015          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017        }
2018      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019        for(i=0; i<BOARD_HEIGHT; i++)
2020          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022              board[i][j];
2023      }
2024      gameInfo.boardWidth  = newWidth;
2025      gameInfo.boardHeight = newHeight;
2026      gameInfo.holdingsWidth = newHoldingsWidth;
2027      gameInfo.variant = newVariant;
2028      InitDrawingSizes(-2, 0);
2029    } else gameInfo.variant = newVariant;
2030    CopyBoard(oldBoard, board);   // remember correctly formatted board
2031      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2032    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2033 }
2034
2035 static int loggedOn = FALSE;
2036
2037 /*-- Game start info cache: --*/
2038 int gs_gamenum;
2039 char gs_kind[MSG_SIZ];
2040 static char player1Name[128] = "";
2041 static char player2Name[128] = "";
2042 static char cont_seq[] = "\n\\   ";
2043 static int player1Rating = -1;
2044 static int player2Rating = -1;
2045 /*----------------------------*/
2046
2047 ColorClass curColor = ColorNormal;
2048 int suppressKibitz = 0;
2049
2050 void
2051 read_from_ics(isr, closure, data, count, error)
2052      InputSourceRef isr;
2053      VOIDSTAR closure;
2054      char *data;
2055      int count;
2056      int error;
2057 {
2058 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2059 #define STARTED_NONE 0
2060 #define STARTED_MOVES 1
2061 #define STARTED_BOARD 2
2062 #define STARTED_OBSERVE 3
2063 #define STARTED_HOLDINGS 4
2064 #define STARTED_CHATTER 5
2065 #define STARTED_COMMENT 6
2066 #define STARTED_MOVES_NOHIDE 7
2067     
2068     static int started = STARTED_NONE;
2069     static char parse[20000];
2070     static int parse_pos = 0;
2071     static char buf[BUF_SIZE + 1];
2072     static int firstTime = TRUE, intfSet = FALSE;
2073     static ColorClass prevColor = ColorNormal;
2074     static int savingComment = FALSE;
2075     static int cmatch = 0; // continuation sequence match
2076     char *bp;
2077     char str[500];
2078     int i, oldi;
2079     int buf_len;
2080     int next_out;
2081     int tkind;
2082     int backup;    /* [DM] For zippy color lines */
2083     char *p;
2084     char talker[MSG_SIZ]; // [HGM] chat
2085     int channel;
2086
2087     if (appData.debugMode) {
2088       if (!error) {
2089         fprintf(debugFP, "<ICS: ");
2090         show_bytes(debugFP, data, count);
2091         fprintf(debugFP, "\n");
2092       }
2093     }
2094
2095     if (appData.debugMode) { int f = forwardMostMove;
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2098                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2099     }
2100     if (count > 0) {
2101         /* If last read ended with a partial line that we couldn't parse,
2102            prepend it to the new read and try again. */
2103         if (leftover_len > 0) {
2104             for (i=0; i<leftover_len; i++)
2105               buf[i] = buf[leftover_start + i];
2106         }
2107
2108     /* copy new characters into the buffer */
2109     bp = buf + leftover_len;
2110     buf_len=leftover_len;
2111     for (i=0; i<count; i++)
2112     {
2113         // ignore these
2114         if (data[i] == '\r')
2115             continue;
2116
2117         // join lines split by ICS?
2118         if (!appData.noJoin)
2119         {
2120             /*
2121                 Joining just consists of finding matches against the
2122                 continuation sequence, and discarding that sequence
2123                 if found instead of copying it.  So, until a match
2124                 fails, there's nothing to do since it might be the
2125                 complete sequence, and thus, something we don't want
2126                 copied.
2127             */
2128             if (data[i] == cont_seq[cmatch])
2129             {
2130                 cmatch++;
2131                 if (cmatch == strlen(cont_seq))
2132                 {
2133                     cmatch = 0; // complete match.  just reset the counter
2134
2135                     /*
2136                         it's possible for the ICS to not include the space
2137                         at the end of the last word, making our [correct]
2138                         join operation fuse two separate words.  the server
2139                         does this when the space occurs at the width setting.
2140                     */
2141                     if (!buf_len || buf[buf_len-1] != ' ')
2142                     {
2143                         *bp++ = ' ';
2144                         buf_len++;
2145                     }
2146                 }
2147                 continue;
2148             }
2149             else if (cmatch)
2150             {
2151                 /*
2152                     match failed, so we have to copy what matched before
2153                     falling through and copying this character.  In reality,
2154                     this will only ever be just the newline character, but
2155                     it doesn't hurt to be precise.
2156                 */
2157                 strncpy(bp, cont_seq, cmatch);
2158                 bp += cmatch;
2159                 buf_len += cmatch;
2160                 cmatch = 0;
2161             }
2162         }
2163
2164         // copy this char
2165         *bp++ = data[i];
2166         buf_len++;
2167     }
2168
2169         buf[buf_len] = NULLCHAR;
2170         next_out = leftover_len;
2171         leftover_start = 0;
2172         
2173         i = 0;
2174         while (i < buf_len) {
2175             /* Deal with part of the TELNET option negotiation
2176                protocol.  We refuse to do anything beyond the
2177                defaults, except that we allow the WILL ECHO option,
2178                which ICS uses to turn off password echoing when we are
2179                directly connected to it.  We reject this option
2180                if localLineEditing mode is on (always on in xboard)
2181                and we are talking to port 23, which might be a real
2182                telnet server that will try to keep WILL ECHO on permanently.
2183              */
2184             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2185                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2186                 unsigned char option;
2187                 oldi = i;
2188                 switch ((unsigned char) buf[++i]) {
2189                   case TN_WILL:
2190                     if (appData.debugMode)
2191                       fprintf(debugFP, "\n<WILL ");
2192                     switch (option = (unsigned char) buf[++i]) {
2193                       case TN_ECHO:
2194                         if (appData.debugMode)
2195                           fprintf(debugFP, "ECHO ");
2196                         /* Reply only if this is a change, according
2197                            to the protocol rules. */
2198                         if (remoteEchoOption) break;
2199                         if (appData.localLineEditing &&
2200                             atoi(appData.icsPort) == TN_PORT) {
2201                             TelnetRequest(TN_DONT, TN_ECHO);
2202                         } else {
2203                             EchoOff();
2204                             TelnetRequest(TN_DO, TN_ECHO);
2205                             remoteEchoOption = TRUE;
2206                         }
2207                         break;
2208                       default:
2209                         if (appData.debugMode)
2210                           fprintf(debugFP, "%d ", option);
2211                         /* Whatever this is, we don't want it. */
2212                         TelnetRequest(TN_DONT, option);
2213                         break;
2214                     }
2215                     break;
2216                   case TN_WONT:
2217                     if (appData.debugMode)
2218                       fprintf(debugFP, "\n<WONT ");
2219                     switch (option = (unsigned char) buf[++i]) {
2220                       case TN_ECHO:
2221                         if (appData.debugMode)
2222                           fprintf(debugFP, "ECHO ");
2223                         /* Reply only if this is a change, according
2224                            to the protocol rules. */
2225                         if (!remoteEchoOption) break;
2226                         EchoOn();
2227                         TelnetRequest(TN_DONT, TN_ECHO);
2228                         remoteEchoOption = FALSE;
2229                         break;
2230                       default:
2231                         if (appData.debugMode)
2232                           fprintf(debugFP, "%d ", (unsigned char) option);
2233                         /* Whatever this is, it must already be turned
2234                            off, because we never agree to turn on
2235                            anything non-default, so according to the
2236                            protocol rules, we don't reply. */
2237                         break;
2238                     }
2239                     break;
2240                   case TN_DO:
2241                     if (appData.debugMode)
2242                       fprintf(debugFP, "\n<DO ");
2243                     switch (option = (unsigned char) buf[++i]) {
2244                       default:
2245                         /* Whatever this is, we refuse to do it. */
2246                         if (appData.debugMode)
2247                           fprintf(debugFP, "%d ", option);
2248                         TelnetRequest(TN_WONT, option);
2249                         break;
2250                     }
2251                     break;
2252                   case TN_DONT:
2253                     if (appData.debugMode)
2254                       fprintf(debugFP, "\n<DONT ");
2255                     switch (option = (unsigned char) buf[++i]) {
2256                       default:
2257                         if (appData.debugMode)
2258                           fprintf(debugFP, "%d ", option);
2259                         /* Whatever this is, we are already not doing
2260                            it, because we never agree to do anything
2261                            non-default, so according to the protocol
2262                            rules, we don't reply. */
2263                         break;
2264                     }
2265                     break;
2266                   case TN_IAC:
2267                     if (appData.debugMode)
2268                       fprintf(debugFP, "\n<IAC ");
2269                     /* Doubled IAC; pass it through */
2270                     i--;
2271                     break;
2272                   default:
2273                     if (appData.debugMode)
2274                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2275                     /* Drop all other telnet commands on the floor */
2276                     break;
2277                 }
2278                 if (oldi > next_out)
2279                   SendToPlayer(&buf[next_out], oldi - next_out);
2280                 if (++i > next_out)
2281                   next_out = i;
2282                 continue;
2283             }
2284                 
2285             /* OK, this at least will *usually* work */
2286             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2287                 loggedOn = TRUE;
2288             }
2289             
2290             if (loggedOn && !intfSet) {
2291                 if (ics_type == ICS_ICC) {
2292                   sprintf(str,
2293                           "/set-quietly interface %s\n/set-quietly style 12\n",
2294                           programVersion);
2295                 } else if (ics_type == ICS_CHESSNET) {
2296                   sprintf(str, "/style 12\n");
2297                 } else {
2298                   strcpy(str, "alias $ @\n$set interface ");
2299                   strcat(str, programVersion);
2300                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2301 #ifdef WIN32
2302                   strcat(str, "$iset nohighlight 1\n");
2303 #endif
2304                   strcat(str, "$iset lock 1\n$style 12\n");
2305                 }
2306                 SendToICS(str);
2307                 NotifyFrontendLogin();
2308                 intfSet = TRUE;
2309             }
2310
2311             if (started == STARTED_COMMENT) {
2312                 /* Accumulate characters in comment */
2313                 parse[parse_pos++] = buf[i];
2314                 if (buf[i] == '\n') {
2315                     parse[parse_pos] = NULLCHAR;
2316                     if(chattingPartner>=0) {
2317                         char mess[MSG_SIZ];
2318                         sprintf(mess, "%s%s", talker, parse);
2319                         OutputChatMessage(chattingPartner, mess);
2320                         chattingPartner = -1;
2321                     } else
2322                     if(!suppressKibitz) // [HGM] kibitz
2323                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2324                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2325                         int nrDigit = 0, nrAlph = 0, i;
2326                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2327                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2328                         parse[parse_pos] = NULLCHAR;
2329                         // try to be smart: if it does not look like search info, it should go to
2330                         // ICS interaction window after all, not to engine-output window.
2331                         for(i=0; i<parse_pos; i++) { // count letters and digits
2332                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2333                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2334                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2335                         }
2336                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2337                             int depth=0; float score;
2338                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2339                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2340                                 pvInfoList[forwardMostMove-1].depth = depth;
2341                                 pvInfoList[forwardMostMove-1].score = 100*score;
2342                             }
2343                             OutputKibitz(suppressKibitz, parse);
2344                         } else {
2345                             char tmp[MSG_SIZ];
2346                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2347                             SendToPlayer(tmp, strlen(tmp));
2348                         }
2349                     }
2350                     started = STARTED_NONE;
2351                 } else {
2352                     /* Don't match patterns against characters in chatter */
2353                     i++;
2354                     continue;
2355                 }
2356             }
2357             if (started == STARTED_CHATTER) {
2358                 if (buf[i] != '\n') {
2359                     /* Don't match patterns against characters in chatter */
2360                     i++;
2361                     continue;
2362                 }
2363                 started = STARTED_NONE;
2364             }
2365
2366             /* Kludge to deal with rcmd protocol */
2367             if (firstTime && looking_at(buf, &i, "\001*")) {
2368                 DisplayFatalError(&buf[1], 0, 1);
2369                 continue;
2370             } else {
2371                 firstTime = FALSE;
2372             }
2373
2374             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2375                 ics_type = ICS_ICC;
2376                 ics_prefix = "/";
2377                 if (appData.debugMode)
2378                   fprintf(debugFP, "ics_type %d\n", ics_type);
2379                 continue;
2380             }
2381             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2382                 ics_type = ICS_FICS;
2383                 ics_prefix = "$";
2384                 if (appData.debugMode)
2385                   fprintf(debugFP, "ics_type %d\n", ics_type);
2386                 continue;
2387             }
2388             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2389                 ics_type = ICS_CHESSNET;
2390                 ics_prefix = "/";
2391                 if (appData.debugMode)
2392                   fprintf(debugFP, "ics_type %d\n", ics_type);
2393                 continue;
2394             }
2395
2396             if (!loggedOn &&
2397                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2398                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2399                  looking_at(buf, &i, "will be \"*\""))) {
2400               strcpy(ics_handle, star_match[0]);
2401               continue;
2402             }
2403
2404             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2405               char buf[MSG_SIZ];
2406               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2407               DisplayIcsInteractionTitle(buf);
2408               have_set_title = TRUE;
2409             }
2410
2411             /* skip finger notes */
2412             if (started == STARTED_NONE &&
2413                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2414                  (buf[i] == '1' && buf[i+1] == '0')) &&
2415                 buf[i+2] == ':' && buf[i+3] == ' ') {
2416               started = STARTED_CHATTER;
2417               i += 3;
2418               continue;
2419             }
2420
2421             /* skip formula vars */
2422             if (started == STARTED_NONE &&
2423                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2424               started = STARTED_CHATTER;
2425               i += 3;
2426               continue;
2427             }
2428
2429             oldi = i;
2430             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2431             if (appData.autoKibitz && started == STARTED_NONE && 
2432                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2433                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2434                 if(looking_at(buf, &i, "* kibitzes: ") &&
2435                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2436                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2437                         suppressKibitz = TRUE;
2438                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2439                                 && (gameMode == IcsPlayingWhite)) ||
2440                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2441                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2442                             started = STARTED_CHATTER; // own kibitz we simply discard
2443                         else {
2444                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2445                             parse_pos = 0; parse[0] = NULLCHAR;
2446                             savingComment = TRUE;
2447                             suppressKibitz = gameMode != IcsObserving ? 2 :
2448                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2449                         } 
2450                         continue;
2451                 } else
2452                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2453                     started = STARTED_CHATTER;
2454                     suppressKibitz = TRUE;
2455                 }
2456             } // [HGM] kibitz: end of patch
2457
2458 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2459
2460             // [HGM] chat: intercept tells by users for which we have an open chat window
2461             channel = -1;
2462             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2463                                            looking_at(buf, &i, "* whispers:") ||
2464                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2465                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2466                 int p;
2467                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2468                 chattingPartner = -1;
2469
2470                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2471                 for(p=0; p<MAX_CHAT; p++) {
2472                     if(channel == atoi(chatPartner[p])) {
2473                     talker[0] = '['; strcat(talker, "]");
2474                     chattingPartner = p; break;
2475                     }
2476                 } else
2477                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2478                 for(p=0; p<MAX_CHAT; p++) {
2479                     if(!strcmp("WHISPER", chatPartner[p])) {
2480                         talker[0] = '['; strcat(talker, "]");
2481                         chattingPartner = p; break;
2482                     }
2483                 }
2484                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2485                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2486                     talker[0] = 0;
2487                     chattingPartner = p; break;
2488                 }
2489                 if(chattingPartner<0) i = oldi; else {
2490                     started = STARTED_COMMENT;
2491                     parse_pos = 0; parse[0] = NULLCHAR;
2492                     savingComment = TRUE;
2493                     suppressKibitz = TRUE;
2494                 }
2495             } // [HGM] chat: end of patch
2496
2497             if (appData.zippyTalk || appData.zippyPlay) {
2498                 /* [DM] Backup address for color zippy lines */
2499                 backup = i;
2500 #if ZIPPY
2501        #ifdef WIN32
2502                if (loggedOn == TRUE)
2503                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2504                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2505        #else
2506                 if (ZippyControl(buf, &i) ||
2507                     ZippyConverse(buf, &i) ||
2508                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2509                       loggedOn = TRUE;
2510                       if (!appData.colorize) continue;
2511                 }
2512        #endif
2513 #endif
2514             } // [DM] 'else { ' deleted
2515                 if (
2516                     /* Regular tells and says */
2517                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2518                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2519                     looking_at(buf, &i, "* says: ") ||
2520                     /* Don't color "message" or "messages" output */
2521                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2522                     looking_at(buf, &i, "*. * at *:*: ") ||
2523                     looking_at(buf, &i, "--* (*:*): ") ||
2524                     /* Message notifications (same color as tells) */
2525                     looking_at(buf, &i, "* has left a message ") ||
2526                     looking_at(buf, &i, "* just sent you a message:\n") ||
2527                     /* Whispers and kibitzes */
2528                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2529                     looking_at(buf, &i, "* kibitzes: ") ||
2530                     /* Channel tells */
2531                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2532
2533                   if (tkind == 1 && strchr(star_match[0], ':')) {
2534                       /* Avoid "tells you:" spoofs in channels */
2535                      tkind = 3;
2536                   }
2537                   if (star_match[0][0] == NULLCHAR ||
2538                       strchr(star_match[0], ' ') ||
2539                       (tkind == 3 && strchr(star_match[1], ' '))) {
2540                     /* Reject bogus matches */
2541                     i = oldi;
2542                   } else {
2543                     if (appData.colorize) {
2544                       if (oldi > next_out) {
2545                         SendToPlayer(&buf[next_out], oldi - next_out);
2546                         next_out = oldi;
2547                       }
2548                       switch (tkind) {
2549                       case 1:
2550                         Colorize(ColorTell, FALSE);
2551                         curColor = ColorTell;
2552                         break;
2553                       case 2:
2554                         Colorize(ColorKibitz, FALSE);
2555                         curColor = ColorKibitz;
2556                         break;
2557                       case 3:
2558                         p = strrchr(star_match[1], '(');
2559                         if (p == NULL) {
2560                           p = star_match[1];
2561                         } else {
2562                           p++;
2563                         }
2564                         if (atoi(p) == 1) {
2565                           Colorize(ColorChannel1, FALSE);
2566                           curColor = ColorChannel1;
2567                         } else {
2568                           Colorize(ColorChannel, FALSE);
2569                           curColor = ColorChannel;
2570                         }
2571                         break;
2572                       case 5:
2573                         curColor = ColorNormal;
2574                         break;
2575                       }
2576                     }
2577                     if (started == STARTED_NONE && appData.autoComment &&
2578                         (gameMode == IcsObserving ||
2579                          gameMode == IcsPlayingWhite ||
2580                          gameMode == IcsPlayingBlack)) {
2581                       parse_pos = i - oldi;
2582                       memcpy(parse, &buf[oldi], parse_pos);
2583                       parse[parse_pos] = NULLCHAR;
2584                       started = STARTED_COMMENT;
2585                       savingComment = TRUE;
2586                     } else {
2587                       started = STARTED_CHATTER;
2588                       savingComment = FALSE;
2589                     }
2590                     loggedOn = TRUE;
2591                     continue;
2592                   }
2593                 }
2594
2595                 if (looking_at(buf, &i, "* s-shouts: ") ||
2596                     looking_at(buf, &i, "* c-shouts: ")) {
2597                     if (appData.colorize) {
2598                         if (oldi > next_out) {
2599                             SendToPlayer(&buf[next_out], oldi - next_out);
2600                             next_out = oldi;
2601                         }
2602                         Colorize(ColorSShout, FALSE);
2603                         curColor = ColorSShout;
2604                     }
2605                     loggedOn = TRUE;
2606                     started = STARTED_CHATTER;
2607                     continue;
2608                 }
2609
2610                 if (looking_at(buf, &i, "--->")) {
2611                     loggedOn = TRUE;
2612                     continue;
2613                 }
2614
2615                 if (looking_at(buf, &i, "* shouts: ") ||
2616                     looking_at(buf, &i, "--> ")) {
2617                     if (appData.colorize) {
2618                         if (oldi > next_out) {
2619                             SendToPlayer(&buf[next_out], oldi - next_out);
2620                             next_out = oldi;
2621                         }
2622                         Colorize(ColorShout, FALSE);
2623                         curColor = ColorShout;
2624                     }
2625                     loggedOn = TRUE;
2626                     started = STARTED_CHATTER;
2627                     continue;
2628                 }
2629
2630                 if (looking_at( buf, &i, "Challenge:")) {
2631                     if (appData.colorize) {
2632                         if (oldi > next_out) {
2633                             SendToPlayer(&buf[next_out], oldi - next_out);
2634                             next_out = oldi;
2635                         }
2636                         Colorize(ColorChallenge, FALSE);
2637                         curColor = ColorChallenge;
2638                     }
2639                     loggedOn = TRUE;
2640                     continue;
2641                 }
2642
2643                 if (looking_at(buf, &i, "* offers you") ||
2644                     looking_at(buf, &i, "* offers to be") ||
2645                     looking_at(buf, &i, "* would like to") ||
2646                     looking_at(buf, &i, "* requests to") ||
2647                     looking_at(buf, &i, "Your opponent offers") ||
2648                     looking_at(buf, &i, "Your opponent requests")) {
2649
2650                     if (appData.colorize) {
2651                         if (oldi > next_out) {
2652                             SendToPlayer(&buf[next_out], oldi - next_out);
2653                             next_out = oldi;
2654                         }
2655                         Colorize(ColorRequest, FALSE);
2656                         curColor = ColorRequest;
2657                     }
2658                     continue;
2659                 }
2660
2661                 if (looking_at(buf, &i, "* (*) seeking")) {
2662                     if (appData.colorize) {
2663                         if (oldi > next_out) {
2664                             SendToPlayer(&buf[next_out], oldi - next_out);
2665                             next_out = oldi;
2666                         }
2667                         Colorize(ColorSeek, FALSE);
2668                         curColor = ColorSeek;
2669                     }
2670                     continue;
2671             }
2672
2673             if (looking_at(buf, &i, "\\   ")) {
2674                 if (prevColor != ColorNormal) {
2675                     if (oldi > next_out) {
2676                         SendToPlayer(&buf[next_out], oldi - next_out);
2677                         next_out = oldi;
2678                     }
2679                     Colorize(prevColor, TRUE);
2680                     curColor = prevColor;
2681                 }
2682                 if (savingComment) {
2683                     parse_pos = i - oldi;
2684                     memcpy(parse, &buf[oldi], parse_pos);
2685                     parse[parse_pos] = NULLCHAR;
2686                     started = STARTED_COMMENT;
2687                 } else {
2688                     started = STARTED_CHATTER;
2689                 }
2690                 continue;
2691             }
2692
2693             if (looking_at(buf, &i, "Black Strength :") ||
2694                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2695                 looking_at(buf, &i, "<10>") ||
2696                 looking_at(buf, &i, "#@#")) {
2697                 /* Wrong board style */
2698                 loggedOn = TRUE;
2699                 SendToICS(ics_prefix);
2700                 SendToICS("set style 12\n");
2701                 SendToICS(ics_prefix);
2702                 SendToICS("refresh\n");
2703                 continue;
2704             }
2705             
2706             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2707                 ICSInitScript();
2708                 have_sent_ICS_logon = 1;
2709                 continue;
2710             }
2711               
2712             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2713                 (looking_at(buf, &i, "\n<12> ") ||
2714                  looking_at(buf, &i, "<12> "))) {
2715                 loggedOn = TRUE;
2716                 if (oldi > next_out) {
2717                     SendToPlayer(&buf[next_out], oldi - next_out);
2718                 }
2719                 next_out = i;
2720                 started = STARTED_BOARD;
2721                 parse_pos = 0;
2722                 continue;
2723             }
2724
2725             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2726                 looking_at(buf, &i, "<b1> ")) {
2727                 if (oldi > next_out) {
2728                     SendToPlayer(&buf[next_out], oldi - next_out);
2729                 }
2730                 next_out = i;
2731                 started = STARTED_HOLDINGS;
2732                 parse_pos = 0;
2733                 continue;
2734             }
2735
2736             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2737                 loggedOn = TRUE;
2738                 /* Header for a move list -- first line */
2739
2740                 switch (ics_getting_history) {
2741                   case H_FALSE:
2742                     switch (gameMode) {
2743                       case IcsIdle:
2744                       case BeginningOfGame:
2745                         /* User typed "moves" or "oldmoves" while we
2746                            were idle.  Pretend we asked for these
2747                            moves and soak them up so user can step
2748                            through them and/or save them.
2749                            */
2750                         Reset(FALSE, TRUE);
2751                         gameMode = IcsObserving;
2752                         ModeHighlight();
2753                         ics_gamenum = -1;
2754                         ics_getting_history = H_GOT_UNREQ_HEADER;
2755                         break;
2756                       case EditGame: /*?*/
2757                       case EditPosition: /*?*/
2758                         /* Should above feature work in these modes too? */
2759                         /* For now it doesn't */
2760                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2761                         break;
2762                       default:
2763                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2764                         break;
2765                     }
2766                     break;
2767                   case H_REQUESTED:
2768                     /* Is this the right one? */
2769                     if (gameInfo.white && gameInfo.black &&
2770                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2771                         strcmp(gameInfo.black, star_match[2]) == 0) {
2772                         /* All is well */
2773                         ics_getting_history = H_GOT_REQ_HEADER;
2774                     }
2775                     break;
2776                   case H_GOT_REQ_HEADER:
2777                   case H_GOT_UNREQ_HEADER:
2778                   case H_GOT_UNWANTED_HEADER:
2779                   case H_GETTING_MOVES:
2780                     /* Should not happen */
2781                     DisplayError(_("Error gathering move list: two headers"), 0);
2782                     ics_getting_history = H_FALSE;
2783                     break;
2784                 }
2785
2786                 /* Save player ratings into gameInfo if needed */
2787                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2788                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2789                     (gameInfo.whiteRating == -1 ||
2790                      gameInfo.blackRating == -1)) {
2791
2792                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2793                     gameInfo.blackRating = string_to_rating(star_match[3]);
2794                     if (appData.debugMode)
2795                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2796                               gameInfo.whiteRating, gameInfo.blackRating);
2797                 }
2798                 continue;
2799             }
2800
2801             if (looking_at(buf, &i,
2802               "* * match, initial time: * minute*, increment: * second")) {
2803                 /* Header for a move list -- second line */
2804                 /* Initial board will follow if this is a wild game */
2805                 if (gameInfo.event != NULL) free(gameInfo.event);
2806                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2807                 gameInfo.event = StrSave(str);
2808                 /* [HGM] we switched variant. Translate boards if needed. */
2809                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2810                 continue;
2811             }
2812
2813             if (looking_at(buf, &i, "Move  ")) {
2814                 /* Beginning of a move list */
2815                 switch (ics_getting_history) {
2816                   case H_FALSE:
2817                     /* Normally should not happen */
2818                     /* Maybe user hit reset while we were parsing */
2819                     break;
2820                   case H_REQUESTED:
2821                     /* Happens if we are ignoring a move list that is not
2822                      * the one we just requested.  Common if the user
2823                      * tries to observe two games without turning off
2824                      * getMoveList */
2825                     break;
2826                   case H_GETTING_MOVES:
2827                     /* Should not happen */
2828                     DisplayError(_("Error gathering move list: nested"), 0);
2829                     ics_getting_history = H_FALSE;
2830                     break;
2831                   case H_GOT_REQ_HEADER:
2832                     ics_getting_history = H_GETTING_MOVES;
2833                     started = STARTED_MOVES;
2834                     parse_pos = 0;
2835                     if (oldi > next_out) {
2836                         SendToPlayer(&buf[next_out], oldi - next_out);
2837                     }
2838                     break;
2839                   case H_GOT_UNREQ_HEADER:
2840                     ics_getting_history = H_GETTING_MOVES;
2841                     started = STARTED_MOVES_NOHIDE;
2842                     parse_pos = 0;
2843                     break;
2844                   case H_GOT_UNWANTED_HEADER:
2845                     ics_getting_history = H_FALSE;
2846                     break;
2847                 }
2848                 continue;
2849             }                           
2850             
2851             if (looking_at(buf, &i, "% ") ||
2852                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2853                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2854                 savingComment = FALSE;
2855                 switch (started) {
2856                   case STARTED_MOVES:
2857                   case STARTED_MOVES_NOHIDE:
2858                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2859                     parse[parse_pos + i - oldi] = NULLCHAR;
2860                     ParseGameHistory(parse);
2861 #if ZIPPY
2862                     if (appData.zippyPlay && first.initDone) {
2863                         FeedMovesToProgram(&first, forwardMostMove);
2864                         if (gameMode == IcsPlayingWhite) {
2865                             if (WhiteOnMove(forwardMostMove)) {
2866                                 if (first.sendTime) {
2867                                   if (first.useColors) {
2868                                     SendToProgram("black\n", &first); 
2869                                   }
2870                                   SendTimeRemaining(&first, TRUE);
2871                                 }
2872                                 if (first.useColors) {
2873                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2874                                 }
2875                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2876                                 first.maybeThinking = TRUE;
2877                             } else {
2878                                 if (first.usePlayother) {
2879                                   if (first.sendTime) {
2880                                     SendTimeRemaining(&first, TRUE);
2881                                   }
2882                                   SendToProgram("playother\n", &first);
2883                                   firstMove = FALSE;
2884                                 } else {
2885                                   firstMove = TRUE;
2886                                 }
2887                             }
2888                         } else if (gameMode == IcsPlayingBlack) {
2889                             if (!WhiteOnMove(forwardMostMove)) {
2890                                 if (first.sendTime) {
2891                                   if (first.useColors) {
2892                                     SendToProgram("white\n", &first);
2893                                   }
2894                                   SendTimeRemaining(&first, FALSE);
2895                                 }
2896                                 if (first.useColors) {
2897                                   SendToProgram("black\n", &first);
2898                                 }
2899                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2900                                 first.maybeThinking = TRUE;
2901                             } else {
2902                                 if (first.usePlayother) {
2903                                   if (first.sendTime) {
2904                                     SendTimeRemaining(&first, FALSE);
2905                                   }
2906                                   SendToProgram("playother\n", &first);
2907                                   firstMove = FALSE;
2908                                 } else {
2909                                   firstMove = TRUE;
2910                                 }
2911                             }
2912                         }                       
2913                     }
2914 #endif
2915                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2916                         /* Moves came from oldmoves or moves command
2917                            while we weren't doing anything else.
2918                            */
2919                         currentMove = forwardMostMove;
2920                         ClearHighlights();/*!!could figure this out*/
2921                         flipView = appData.flipView;
2922                         DrawPosition(TRUE, boards[currentMove]);
2923                         DisplayBothClocks();
2924                         sprintf(str, "%s vs. %s",
2925                                 gameInfo.white, gameInfo.black);
2926                         DisplayTitle(str);
2927                         gameMode = IcsIdle;
2928                     } else {
2929                         /* Moves were history of an active game */
2930                         if (gameInfo.resultDetails != NULL) {
2931                             free(gameInfo.resultDetails);
2932                             gameInfo.resultDetails = NULL;
2933                         }
2934                     }
2935                     HistorySet(parseList, backwardMostMove,
2936                                forwardMostMove, currentMove-1);
2937                     DisplayMove(currentMove - 1);
2938                     if (started == STARTED_MOVES) next_out = i;
2939                     started = STARTED_NONE;
2940                     ics_getting_history = H_FALSE;
2941                     break;
2942
2943                   case STARTED_OBSERVE:
2944                     started = STARTED_NONE;
2945                     SendToICS(ics_prefix);
2946                     SendToICS("refresh\n");
2947                     break;
2948
2949                   default:
2950                     break;
2951                 }
2952                 if(bookHit) { // [HGM] book: simulate book reply
2953                     static char bookMove[MSG_SIZ]; // a bit generous?
2954
2955                     programStats.nodes = programStats.depth = programStats.time = 
2956                     programStats.score = programStats.got_only_move = 0;
2957                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2958
2959                     strcpy(bookMove, "move ");
2960                     strcat(bookMove, bookHit);
2961                     HandleMachineMove(bookMove, &first);
2962                 }
2963                 continue;
2964             }
2965             
2966             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2967                  started == STARTED_HOLDINGS ||
2968                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2969                 /* Accumulate characters in move list or board */
2970                 parse[parse_pos++] = buf[i];
2971             }
2972             
2973             /* Start of game messages.  Mostly we detect start of game
2974                when the first board image arrives.  On some versions
2975                of the ICS, though, we need to do a "refresh" after starting
2976                to observe in order to get the current board right away. */
2977             if (looking_at(buf, &i, "Adding game * to observation list")) {
2978                 started = STARTED_OBSERVE;
2979                 continue;
2980             }
2981
2982             /* Handle auto-observe */
2983             if (appData.autoObserve &&
2984                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2985                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2986                 char *player;
2987                 /* Choose the player that was highlighted, if any. */
2988                 if (star_match[0][0] == '\033' ||
2989                     star_match[1][0] != '\033') {
2990                     player = star_match[0];
2991                 } else {
2992                     player = star_match[2];
2993                 }
2994                 sprintf(str, "%sobserve %s\n",
2995                         ics_prefix, StripHighlightAndTitle(player));
2996                 SendToICS(str);
2997
2998                 /* Save ratings from notify string */
2999                 strcpy(player1Name, star_match[0]);
3000                 player1Rating = string_to_rating(star_match[1]);
3001                 strcpy(player2Name, star_match[2]);
3002                 player2Rating = string_to_rating(star_match[3]);
3003
3004                 if (appData.debugMode)
3005                   fprintf(debugFP, 
3006                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3007                           player1Name, player1Rating,
3008                           player2Name, player2Rating);
3009
3010                 continue;
3011             }
3012
3013             /* Deal with automatic examine mode after a game,
3014                and with IcsObserving -> IcsExamining transition */
3015             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3016                 looking_at(buf, &i, "has made you an examiner of game *")) {
3017
3018                 int gamenum = atoi(star_match[0]);
3019                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3020                     gamenum == ics_gamenum) {
3021                     /* We were already playing or observing this game;
3022                        no need to refetch history */
3023                     gameMode = IcsExamining;
3024                     if (pausing) {
3025                         pauseExamForwardMostMove = forwardMostMove;
3026                     } else if (currentMove < forwardMostMove) {
3027                         ForwardInner(forwardMostMove);
3028                     }
3029                 } else {
3030                     /* I don't think this case really can happen */
3031                     SendToICS(ics_prefix);
3032                     SendToICS("refresh\n");
3033                 }
3034                 continue;
3035             }    
3036             
3037             /* Error messages */
3038 //          if (ics_user_moved) {
3039             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3040                 if (looking_at(buf, &i, "Illegal move") ||
3041                     looking_at(buf, &i, "Not a legal move") ||
3042                     looking_at(buf, &i, "Your king is in check") ||
3043                     looking_at(buf, &i, "It isn't your turn") ||
3044                     looking_at(buf, &i, "It is not your move")) {
3045                     /* Illegal move */
3046                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3047                         currentMove = --forwardMostMove;
3048                         DisplayMove(currentMove - 1); /* before DMError */
3049                         DrawPosition(FALSE, boards[currentMove]);
3050                         SwitchClocks();
3051                         DisplayBothClocks();
3052                     }
3053                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3054                     ics_user_moved = 0;
3055                     continue;
3056                 }
3057             }
3058
3059             if (looking_at(buf, &i, "still have time") ||
3060                 looking_at(buf, &i, "not out of time") ||
3061                 looking_at(buf, &i, "either player is out of time") ||
3062                 looking_at(buf, &i, "has timeseal; checking")) {
3063                 /* We must have called his flag a little too soon */
3064                 whiteFlag = blackFlag = FALSE;
3065                 continue;
3066             }
3067
3068             if (looking_at(buf, &i, "added * seconds to") ||
3069                 looking_at(buf, &i, "seconds were added to")) {
3070                 /* Update the clocks */
3071                 SendToICS(ics_prefix);
3072                 SendToICS("refresh\n");
3073                 continue;
3074             }
3075
3076             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3077                 ics_clock_paused = TRUE;
3078                 StopClocks();
3079                 continue;
3080             }
3081
3082             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3083                 ics_clock_paused = FALSE;
3084                 StartClocks();
3085                 continue;
3086             }
3087
3088             /* Grab player ratings from the Creating: message.
3089                Note we have to check for the special case when
3090                the ICS inserts things like [white] or [black]. */
3091             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3092                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3093                 /* star_matches:
3094                    0    player 1 name (not necessarily white)
3095                    1    player 1 rating
3096                    2    empty, white, or black (IGNORED)
3097                    3    player 2 name (not necessarily black)
3098                    4    player 2 rating
3099                    
3100                    The names/ratings are sorted out when the game
3101                    actually starts (below).
3102                 */
3103                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3104                 player1Rating = string_to_rating(star_match[1]);
3105                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3106                 player2Rating = string_to_rating(star_match[4]);
3107
3108                 if (appData.debugMode)
3109                   fprintf(debugFP, 
3110                           "Ratings from 'Creating:' %s %d, %s %d\n",
3111                           player1Name, player1Rating,
3112                           player2Name, player2Rating);
3113
3114                 continue;
3115             }
3116             
3117             /* Improved generic start/end-of-game messages */
3118             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3119                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3120                 /* If tkind == 0: */
3121                 /* star_match[0] is the game number */
3122                 /*           [1] is the white player's name */
3123                 /*           [2] is the black player's name */
3124                 /* For end-of-game: */
3125                 /*           [3] is the reason for the game end */
3126                 /*           [4] is a PGN end game-token, preceded by " " */
3127                 /* For start-of-game: */
3128                 /*           [3] begins with "Creating" or "Continuing" */
3129                 /*           [4] is " *" or empty (don't care). */
3130                 int gamenum = atoi(star_match[0]);
3131                 char *whitename, *blackname, *why, *endtoken;
3132                 ChessMove endtype = (ChessMove) 0;
3133
3134                 if (tkind == 0) {
3135                   whitename = star_match[1];
3136                   blackname = star_match[2];
3137                   why = star_match[3];
3138                   endtoken = star_match[4];
3139                 } else {
3140                   whitename = star_match[1];
3141                   blackname = star_match[3];
3142                   why = star_match[5];
3143                   endtoken = star_match[6];
3144                 }
3145
3146                 /* Game start messages */
3147                 if (strncmp(why, "Creating ", 9) == 0 ||
3148                     strncmp(why, "Continuing ", 11) == 0) {
3149                     gs_gamenum = gamenum;
3150                     strcpy(gs_kind, strchr(why, ' ') + 1);
3151 #if ZIPPY
3152                     if (appData.zippyPlay) {
3153                         ZippyGameStart(whitename, blackname);
3154                     }
3155 #endif /*ZIPPY*/
3156                     continue;
3157                 }
3158
3159                 /* Game end messages */
3160                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3161                     ics_gamenum != gamenum) {
3162                     continue;
3163                 }
3164                 while (endtoken[0] == ' ') endtoken++;
3165                 switch (endtoken[0]) {
3166                   case '*':
3167                   default:
3168                     endtype = GameUnfinished;
3169                     break;
3170                   case '0':
3171                     endtype = BlackWins;
3172                     break;
3173                   case '1':
3174                     if (endtoken[1] == '/')
3175                       endtype = GameIsDrawn;
3176                     else
3177                       endtype = WhiteWins;
3178                     break;
3179                 }
3180                 GameEnds(endtype, why, GE_ICS);
3181 #if ZIPPY
3182                 if (appData.zippyPlay && first.initDone) {
3183                     ZippyGameEnd(endtype, why);
3184                     if (first.pr == NULL) {
3185                       /* Start the next process early so that we'll
3186                          be ready for the next challenge */
3187                       StartChessProgram(&first);
3188                     }
3189                     /* Send "new" early, in case this command takes
3190                        a long time to finish, so that we'll be ready
3191                        for the next challenge. */
3192                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3193                     Reset(TRUE, TRUE);
3194                 }
3195 #endif /*ZIPPY*/
3196                 continue;
3197             }
3198
3199             if (looking_at(buf, &i, "Removing game * from observation") ||
3200                 looking_at(buf, &i, "no longer observing game *") ||
3201                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3202                 if (gameMode == IcsObserving &&
3203                     atoi(star_match[0]) == ics_gamenum)
3204                   {
3205                       /* icsEngineAnalyze */
3206                       if (appData.icsEngineAnalyze) {
3207                             ExitAnalyzeMode();
3208                             ModeHighlight();
3209                       }
3210                       StopClocks();
3211                       gameMode = IcsIdle;
3212                       ics_gamenum = -1;
3213                       ics_user_moved = FALSE;
3214                   }
3215                 continue;
3216             }
3217
3218             if (looking_at(buf, &i, "no longer examining game *")) {
3219                 if (gameMode == IcsExamining &&
3220                     atoi(star_match[0]) == ics_gamenum)
3221                   {
3222                       gameMode = IcsIdle;
3223                       ics_gamenum = -1;
3224                       ics_user_moved = FALSE;
3225                   }
3226                 continue;
3227             }
3228
3229             /* Advance leftover_start past any newlines we find,
3230                so only partial lines can get reparsed */
3231             if (looking_at(buf, &i, "\n")) {
3232                 prevColor = curColor;
3233                 if (curColor != ColorNormal) {
3234                     if (oldi > next_out) {
3235                         SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = oldi;
3237                     }
3238                     Colorize(ColorNormal, FALSE);
3239                     curColor = ColorNormal;
3240                 }
3241                 if (started == STARTED_BOARD) {
3242                     started = STARTED_NONE;
3243                     parse[parse_pos] = NULLCHAR;
3244                     ParseBoard12(parse);
3245                     ics_user_moved = 0;
3246
3247                     /* Send premove here */
3248                     if (appData.premove) {
3249                       char str[MSG_SIZ];
3250                       if (currentMove == 0 &&
3251                           gameMode == IcsPlayingWhite &&
3252                           appData.premoveWhite) {
3253                         sprintf(str, "%s\n", appData.premoveWhiteText);
3254                         if (appData.debugMode)
3255                           fprintf(debugFP, "Sending premove:\n");
3256                         SendToICS(str);
3257                       } else if (currentMove == 1 &&
3258                                  gameMode == IcsPlayingBlack &&
3259                                  appData.premoveBlack) {
3260                         sprintf(str, "%s\n", appData.premoveBlackText);
3261                         if (appData.debugMode)
3262                           fprintf(debugFP, "Sending premove:\n");
3263                         SendToICS(str);
3264                       } else if (gotPremove) {
3265                         gotPremove = 0;
3266                         ClearPremoveHighlights();
3267                         if (appData.debugMode)
3268                           fprintf(debugFP, "Sending premove:\n");
3269                           UserMoveEvent(premoveFromX, premoveFromY, 
3270                                         premoveToX, premoveToY, 
3271                                         premovePromoChar);
3272                       }
3273                     }
3274
3275                     /* Usually suppress following prompt */
3276                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3277                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3278                         if (looking_at(buf, &i, "*% ")) {
3279                             savingComment = FALSE;
3280                         }
3281                     }
3282                     next_out = i;
3283                 } else if (started == STARTED_HOLDINGS) {
3284                     int gamenum;
3285                     char new_piece[MSG_SIZ];
3286                     started = STARTED_NONE;
3287                     parse[parse_pos] = NULLCHAR;
3288                     if (appData.debugMode)
3289                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3290                                                         parse, currentMove);
3291                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3292                         gamenum == ics_gamenum) {
3293                         if (gameInfo.variant == VariantNormal) {
3294                           /* [HGM] We seem to switch variant during a game!
3295                            * Presumably no holdings were displayed, so we have
3296                            * to move the position two files to the right to
3297                            * create room for them!
3298                            */
3299                           VariantClass newVariant;
3300                           switch(gameInfo.boardWidth) { // base guess on board width
3301                                 case 9:  newVariant = VariantShogi; break;
3302                                 case 10: newVariant = VariantGreat; break;
3303                                 default: newVariant = VariantCrazyhouse; break;
3304                           }
3305                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3306                           /* Get a move list just to see the header, which
3307                              will tell us whether this is really bug or zh */
3308                           if (ics_getting_history == H_FALSE) {
3309                             ics_getting_history = H_REQUESTED;
3310                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3311                             SendToICS(str);
3312                           }
3313                         }
3314                         new_piece[0] = NULLCHAR;
3315                         sscanf(parse, "game %d white [%s black [%s <- %s",
3316                                &gamenum, white_holding, black_holding,
3317                                new_piece);
3318                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3319                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3320                         /* [HGM] copy holdings to board holdings area */
3321                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3322                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3323                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3324 #if ZIPPY
3325                         if (appData.zippyPlay && first.initDone) {
3326                             ZippyHoldings(white_holding, black_holding,
3327                                           new_piece);
3328                         }
3329 #endif /*ZIPPY*/
3330                         if (tinyLayout || smallLayout) {
3331                             char wh[16], bh[16];
3332                             PackHolding(wh, white_holding);
3333                             PackHolding(bh, black_holding);
3334                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3335                                     gameInfo.white, gameInfo.black);
3336                         } else {
3337                             sprintf(str, "%s [%s] vs. %s [%s]",
3338                                     gameInfo.white, white_holding,
3339                                     gameInfo.black, black_holding);
3340                         }
3341
3342                         DrawPosition(FALSE, boards[currentMove]);
3343                         DisplayTitle(str);
3344                     }
3345                     /* Suppress following prompt */
3346                     if (looking_at(buf, &i, "*% ")) {
3347                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3348                         savingComment = FALSE;
3349                     }
3350                     next_out = i;
3351                 }
3352                 continue;
3353             }
3354
3355             i++;                /* skip unparsed character and loop back */
3356         }
3357         
3358         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3359             started != STARTED_HOLDINGS && i > next_out) {
3360             SendToPlayer(&buf[next_out], i - next_out);
3361             next_out = i;
3362         }
3363         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3364         
3365         leftover_len = buf_len - leftover_start;
3366         /* if buffer ends with something we couldn't parse,
3367            reparse it after appending the next read */
3368         
3369     } else if (count == 0) {
3370         RemoveInputSource(isr);
3371         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3372     } else {
3373         DisplayFatalError(_("Error reading from ICS"), error, 1);
3374     }
3375 }
3376
3377
3378 /* Board style 12 looks like this:
3379    
3380    <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
3381    
3382  * The "<12> " is stripped before it gets to this routine.  The two
3383  * trailing 0's (flip state and clock ticking) are later addition, and
3384  * some chess servers may not have them, or may have only the first.
3385  * Additional trailing fields may be added in the future.  
3386  */
3387
3388 #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"
3389
3390 #define RELATION_OBSERVING_PLAYED    0
3391 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3392 #define RELATION_PLAYING_MYMOVE      1
3393 #define RELATION_PLAYING_NOTMYMOVE  -1
3394 #define RELATION_EXAMINING           2
3395 #define RELATION_ISOLATED_BOARD     -3
3396 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3397
3398 void
3399 ParseBoard12(string)
3400      char *string;
3401
3402     GameMode newGameMode;
3403     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3404     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3405     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3406     char to_play, board_chars[200];
3407     char move_str[500], str[500], elapsed_time[500];
3408     char black[32], white[32];
3409     Board board;
3410     int prevMove = currentMove;
3411     int ticking = 2;
3412     ChessMove moveType;
3413     int fromX, fromY, toX, toY;
3414     char promoChar;
3415     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3416     char *bookHit = NULL; // [HGM] book
3417     Boolean weird = FALSE, reqFlag = FALSE;
3418
3419     fromX = fromY = toX = toY = -1;
3420     
3421     newGame = FALSE;
3422
3423     if (appData.debugMode)
3424       fprintf(debugFP, _("Parsing board: %s\n"), string);
3425
3426     move_str[0] = NULLCHAR;
3427     elapsed_time[0] = NULLCHAR;
3428     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3429         int  i = 0, j;
3430         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3431             if(string[i] == ' ') { ranks++; files = 0; }
3432             else files++;
3433             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3434             i++;
3435         }
3436         for(j = 0; j <i; j++) board_chars[j] = string[j];
3437         board_chars[i] = '\0';
3438         string += i + 1;
3439     }
3440     n = sscanf(string, PATTERN, &to_play, &double_push,
3441                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3442                &gamenum, white, black, &relation, &basetime, &increment,
3443                &white_stren, &black_stren, &white_time, &black_time,
3444                &moveNum, str, elapsed_time, move_str, &ics_flip,
3445                &ticking);
3446
3447     if (n < 21) {
3448         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3449         DisplayError(str, 0);
3450         return;
3451     }
3452
3453     /* Convert the move number to internal form */
3454     moveNum = (moveNum - 1) * 2;
3455     if (to_play == 'B') moveNum++;
3456     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3457       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3458                         0, 1);
3459       return;
3460     }
3461     
3462     switch (relation) {
3463       case RELATION_OBSERVING_PLAYED:
3464       case RELATION_OBSERVING_STATIC:
3465         if (gamenum == -1) {
3466             /* Old ICC buglet */
3467             relation = RELATION_OBSERVING_STATIC;
3468         }
3469         newGameMode = IcsObserving;
3470         break;
3471       case RELATION_PLAYING_MYMOVE:
3472       case RELATION_PLAYING_NOTMYMOVE:
3473         newGameMode =
3474           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3475             IcsPlayingWhite : IcsPlayingBlack;
3476         break;
3477       case RELATION_EXAMINING:
3478         newGameMode = IcsExamining;
3479         break;
3480       case RELATION_ISOLATED_BOARD:
3481       default:
3482         /* Just display this board.  If user was doing something else,
3483            we will forget about it until the next board comes. */ 
3484         newGameMode = IcsIdle;
3485         break;
3486       case RELATION_STARTING_POSITION:
3487         newGameMode = gameMode;
3488         break;
3489     }
3490     
3491     /* Modify behavior for initial board display on move listing
3492        of wild games.
3493        */
3494     switch (ics_getting_history) {
3495       case H_FALSE:
3496       case H_REQUESTED:
3497         break;
3498       case H_GOT_REQ_HEADER:
3499       case H_GOT_UNREQ_HEADER:
3500         /* This is the initial position of the current game */
3501         gamenum = ics_gamenum;
3502         moveNum = 0;            /* old ICS bug workaround */
3503         if (to_play == 'B') {
3504           startedFromSetupPosition = TRUE;
3505           blackPlaysFirst = TRUE;
3506           moveNum = 1;
3507           if (forwardMostMove == 0) forwardMostMove = 1;
3508           if (backwardMostMove == 0) backwardMostMove = 1;
3509           if (currentMove == 0) currentMove = 1;
3510         }
3511         newGameMode = gameMode;
3512         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3513         break;
3514       case H_GOT_UNWANTED_HEADER:
3515         /* This is an initial board that we don't want */
3516         return;
3517       case H_GETTING_MOVES:
3518         /* Should not happen */
3519         DisplayError(_("Error gathering move list: extra board"), 0);
3520         ics_getting_history = H_FALSE;
3521         return;
3522     }
3523
3524    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3525                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3526      /* [HGM] We seem to have switched variant unexpectedly
3527       * Try to guess new variant from board size
3528       */
3529           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3530           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3531           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3532           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3533           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3534           if(!weird) newVariant = VariantNormal;
3535           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3536           /* Get a move list just to see the header, which
3537              will tell us whether this is really bug or zh */
3538           if (ics_getting_history == H_FALSE) {
3539             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3540             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3541             SendToICS(str);
3542           }
3543     }
3544     
3545     /* Take action if this is the first board of a new game, or of a
3546        different game than is currently being displayed.  */
3547     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3548         relation == RELATION_ISOLATED_BOARD) {
3549         
3550         /* Forget the old game and get the history (if any) of the new one */
3551         if (gameMode != BeginningOfGame) {
3552           Reset(TRUE, TRUE);
3553         }
3554         newGame = TRUE;
3555         if (appData.autoRaiseBoard) BoardToTop();
3556         prevMove = -3;
3557         if (gamenum == -1) {
3558             newGameMode = IcsIdle;
3559         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3560                    appData.getMoveList && !reqFlag) {
3561             /* Need to get game history */
3562             ics_getting_history = H_REQUESTED;
3563             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3564             SendToICS(str);
3565         }
3566         
3567         /* Initially flip the board to have black on the bottom if playing
3568            black or if the ICS flip flag is set, but let the user change
3569            it with the Flip View button. */
3570         flipView = appData.autoFlipView ? 
3571           (newGameMode == IcsPlayingBlack) || ics_flip :
3572           appData.flipView;
3573         
3574         /* Done with values from previous mode; copy in new ones */
3575         gameMode = newGameMode;
3576         ModeHighlight();
3577         ics_gamenum = gamenum;
3578         if (gamenum == gs_gamenum) {
3579             int klen = strlen(gs_kind);
3580             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3581             sprintf(str, "ICS %s", gs_kind);
3582             gameInfo.event = StrSave(str);
3583         } else {
3584             gameInfo.event = StrSave("ICS game");
3585         }
3586         gameInfo.site = StrSave(appData.icsHost);
3587         gameInfo.date = PGNDate();
3588         gameInfo.round = StrSave("-");
3589         gameInfo.white = StrSave(white);
3590         gameInfo.black = StrSave(black);
3591         timeControl = basetime * 60 * 1000;
3592         timeControl_2 = 0;
3593         timeIncrement = increment * 1000;
3594         movesPerSession = 0;
3595         gameInfo.timeControl = TimeControlTagValue();
3596         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3597   if (appData.debugMode) {
3598     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3599     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3600     setbuf(debugFP, NULL);
3601   }
3602
3603         gameInfo.outOfBook = NULL;
3604         
3605         /* Do we have the ratings? */
3606         if (strcmp(player1Name, white) == 0 &&
3607             strcmp(player2Name, black) == 0) {
3608             if (appData.debugMode)
3609               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3610                       player1Rating, player2Rating);
3611             gameInfo.whiteRating = player1Rating;
3612             gameInfo.blackRating = player2Rating;
3613         } else if (strcmp(player2Name, white) == 0 &&
3614                    strcmp(player1Name, black) == 0) {
3615             if (appData.debugMode)
3616               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3617                       player2Rating, player1Rating);
3618             gameInfo.whiteRating = player2Rating;
3619             gameInfo.blackRating = player1Rating;
3620         }
3621         player1Name[0] = player2Name[0] = NULLCHAR;
3622
3623         /* Silence shouts if requested */
3624         if (appData.quietPlay &&
3625             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3626             SendToICS(ics_prefix);
3627             SendToICS("set shout 0\n");
3628         }
3629     }
3630     
3631     /* Deal with midgame name changes */
3632     if (!newGame) {
3633         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3634             if (gameInfo.white) free(gameInfo.white);
3635             gameInfo.white = StrSave(white);
3636         }
3637         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3638             if (gameInfo.black) free(gameInfo.black);
3639             gameInfo.black = StrSave(black);
3640         }
3641     }
3642     
3643     /* Throw away game result if anything actually changes in examine mode */
3644     if (gameMode == IcsExamining && !newGame) {
3645         gameInfo.result = GameUnfinished;
3646         if (gameInfo.resultDetails != NULL) {
3647             free(gameInfo.resultDetails);
3648             gameInfo.resultDetails = NULL;
3649         }
3650     }
3651     
3652     /* In pausing && IcsExamining mode, we ignore boards coming
3653        in if they are in a different variation than we are. */
3654     if (pauseExamInvalid) return;
3655     if (pausing && gameMode == IcsExamining) {
3656         if (moveNum <= pauseExamForwardMostMove) {
3657             pauseExamInvalid = TRUE;
3658             forwardMostMove = pauseExamForwardMostMove;
3659             return;
3660         }
3661     }
3662     
3663   if (appData.debugMode) {
3664     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3665   }
3666     /* Parse the board */
3667     for (k = 0; k < ranks; k++) {
3668       for (j = 0; j < files; j++)
3669         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3670       if(gameInfo.holdingsWidth > 1) {
3671            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3672            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3673       }
3674     }
3675     CopyBoard(boards[moveNum], board);
3676     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3677     if (moveNum == 0) {
3678         startedFromSetupPosition =
3679           !CompareBoards(board, initialPosition);
3680         if(startedFromSetupPosition)
3681             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3682     }
3683
3684     /* [HGM] Set castling rights. Take the outermost Rooks,
3685        to make it also work for FRC opening positions. Note that board12
3686        is really defective for later FRC positions, as it has no way to
3687        indicate which Rook can castle if they are on the same side of King.
3688        For the initial position we grant rights to the outermost Rooks,
3689        and remember thos rights, and we then copy them on positions
3690        later in an FRC game. This means WB might not recognize castlings with
3691        Rooks that have moved back to their original position as illegal,
3692        but in ICS mode that is not its job anyway.
3693     */
3694     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3695     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3696
3697         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3698             if(board[0][i] == WhiteRook) j = i;
3699         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3700         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3701             if(board[0][i] == WhiteRook) j = i;
3702         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3703         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3704             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3706         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3707             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3708         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3709
3710         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3713         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3714             if(board[BOARD_HEIGHT-1][k] == bKing)
3715                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3716     } else { int r;
3717         r = boards[moveNum][CASTLING][0] = initialRights[0];
3718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3719         r = boards[moveNum][CASTLING][1] = initialRights[1];
3720         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3721         r = boards[moveNum][CASTLING][3] = initialRights[3];
3722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3723         r = boards[moveNum][CASTLING][4] = initialRights[4];
3724         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3725         /* wildcastle kludge: always assume King has rights */
3726         r = boards[moveNum][CASTLING][2] = initialRights[2];
3727         r = boards[moveNum][CASTLING][5] = initialRights[5];
3728     }
3729     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3730     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3731
3732     
3733     if (ics_getting_history == H_GOT_REQ_HEADER ||
3734         ics_getting_history == H_GOT_UNREQ_HEADER) {
3735         /* This was an initial position from a move list, not
3736            the current position */
3737         return;
3738     }
3739     
3740     /* Update currentMove and known move number limits */
3741     newMove = newGame || moveNum > forwardMostMove;
3742
3743     if (newGame) {
3744         forwardMostMove = backwardMostMove = currentMove = moveNum;
3745         if (gameMode == IcsExamining && moveNum == 0) {
3746           /* Workaround for ICS limitation: we are not told the wild
3747              type when starting to examine a game.  But if we ask for
3748              the move list, the move list header will tell us */
3749             ics_getting_history = H_REQUESTED;
3750             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3751             SendToICS(str);
3752         }
3753     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3754                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3755 #if ZIPPY
3756         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3757         /* [HGM] applied this also to an engine that is silently watching        */
3758         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3759             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3760             gameInfo.variant == currentlyInitializedVariant) {
3761           takeback = forwardMostMove - moveNum;
3762           for (i = 0; i < takeback; i++) {
3763             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3764             SendToProgram("undo\n", &first);
3765           }
3766         }
3767 #endif
3768
3769         forwardMostMove = moveNum;
3770         if (!pausing || currentMove > forwardMostMove)
3771           currentMove = forwardMostMove;
3772     } else {
3773         /* New part of history that is not contiguous with old part */ 
3774         if (pausing && gameMode == IcsExamining) {
3775             pauseExamInvalid = TRUE;
3776             forwardMostMove = pauseExamForwardMostMove;
3777             return;
3778         }
3779         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3780 #if ZIPPY
3781             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3782                 // [HGM] when we will receive the move list we now request, it will be
3783                 // fed to the engine from the first move on. So if the engine is not
3784                 // in the initial position now, bring it there.
3785                 InitChessProgram(&first, 0);
3786             }
3787 #endif
3788             ics_getting_history = H_REQUESTED;
3789             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3790             SendToICS(str);
3791         }
3792         forwardMostMove = backwardMostMove = currentMove = moveNum;
3793     }
3794     
3795     /* Update the clocks */
3796     if (strchr(elapsed_time, '.')) {
3797       /* Time is in ms */
3798       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3799       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3800     } else {
3801       /* Time is in seconds */
3802       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3803       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3804     }
3805       
3806
3807 #if ZIPPY
3808     if (appData.zippyPlay && newGame &&
3809         gameMode != IcsObserving && gameMode != IcsIdle &&
3810         gameMode != IcsExamining)
3811       ZippyFirstBoard(moveNum, basetime, increment);
3812 #endif
3813     
3814     /* Put the move on the move list, first converting
3815        to canonical algebraic form. */
3816     if (moveNum > 0) {
3817   if (appData.debugMode) {
3818     if (appData.debugMode) { int f = forwardMostMove;
3819         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3820                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3821                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3822     }
3823     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3824     fprintf(debugFP, "moveNum = %d\n", moveNum);
3825     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3826     setbuf(debugFP, NULL);
3827   }
3828         if (moveNum <= backwardMostMove) {
3829             /* We don't know what the board looked like before
3830                this move.  Punt. */
3831             strcpy(parseList[moveNum - 1], move_str);
3832             strcat(parseList[moveNum - 1], " ");
3833             strcat(parseList[moveNum - 1], elapsed_time);
3834             moveList[moveNum - 1][0] = NULLCHAR;
3835         } else if (strcmp(move_str, "none") == 0) {
3836             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3837             /* Again, we don't know what the board looked like;
3838                this is really the start of the game. */
3839             parseList[moveNum - 1][0] = NULLCHAR;
3840             moveList[moveNum - 1][0] = NULLCHAR;
3841             backwardMostMove = moveNum;
3842             startedFromSetupPosition = TRUE;
3843             fromX = fromY = toX = toY = -1;
3844         } else {
3845           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3846           //                 So we parse the long-algebraic move string in stead of the SAN move
3847           int valid; char buf[MSG_SIZ], *prom;
3848
3849           // str looks something like "Q/a1-a2"; kill the slash
3850           if(str[1] == '/') 
3851                 sprintf(buf, "%c%s", str[0], str+2);
3852           else  strcpy(buf, str); // might be castling
3853           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3854                 strcat(buf, prom); // long move lacks promo specification!
3855           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3856                 if(appData.debugMode) 
3857                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3858                 strcpy(move_str, buf);
3859           }
3860           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3861                                 &fromX, &fromY, &toX, &toY, &promoChar)
3862                || ParseOneMove(buf, moveNum - 1, &moveType,
3863                                 &fromX, &fromY, &toX, &toY, &promoChar);
3864           // end of long SAN patch
3865           if (valid) {
3866             (void) CoordsToAlgebraic(boards[moveNum - 1],
3867                                      PosFlags(moveNum - 1),
3868                                      fromY, fromX, toY, toX, promoChar,
3869                                      parseList[moveNum-1]);
3870             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3871               case MT_NONE:
3872               case MT_STALEMATE:
3873               default:
3874                 break;
3875               case MT_CHECK:
3876                 if(gameInfo.variant != VariantShogi)
3877                     strcat(parseList[moveNum - 1], "+");
3878                 break;
3879               case MT_CHECKMATE:
3880               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3881                 strcat(parseList[moveNum - 1], "#");
3882                 break;
3883             }
3884             strcat(parseList[moveNum - 1], " ");
3885             strcat(parseList[moveNum - 1], elapsed_time);
3886             /* currentMoveString is set as a side-effect of ParseOneMove */
3887             strcpy(moveList[moveNum - 1], currentMoveString);
3888             strcat(moveList[moveNum - 1], "\n");
3889           } else {
3890             /* Move from ICS was illegal!?  Punt. */
3891   if (appData.debugMode) {
3892     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3893     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3894   }
3895             strcpy(parseList[moveNum - 1], move_str);
3896             strcat(parseList[moveNum - 1], " ");
3897             strcat(parseList[moveNum - 1], elapsed_time);
3898             moveList[moveNum - 1][0] = NULLCHAR;
3899             fromX = fromY = toX = toY = -1;
3900           }
3901         }
3902   if (appData.debugMode) {
3903     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3904     setbuf(debugFP, NULL);
3905   }
3906
3907 #if ZIPPY
3908         /* Send move to chess program (BEFORE animating it). */
3909         if (appData.zippyPlay && !newGame && newMove && 
3910            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3911
3912             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3913                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3914                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3915                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3916                             move_str);
3917                     DisplayError(str, 0);
3918                 } else {
3919                     if (first.sendTime) {
3920                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3921                     }
3922                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3923                     if (firstMove && !bookHit) {
3924                         firstMove = FALSE;
3925                         if (first.useColors) {
3926                           SendToProgram(gameMode == IcsPlayingWhite ?
3927                                         "white\ngo\n" :
3928                                         "black\ngo\n", &first);
3929                         } else {
3930                           SendToProgram("go\n", &first);
3931                         }
3932                         first.maybeThinking = TRUE;
3933                     }
3934                 }
3935             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3936               if (moveList[moveNum - 1][0] == NULLCHAR) {
3937                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3938                 DisplayError(str, 0);
3939               } else {
3940                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3941                 SendMoveToProgram(moveNum - 1, &first);
3942               }
3943             }
3944         }
3945 #endif
3946     }
3947
3948     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3949         /* If move comes from a remote source, animate it.  If it
3950            isn't remote, it will have already been animated. */
3951         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3952             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3953         }
3954         if (!pausing && appData.highlightLastMove) {
3955             SetHighlights(fromX, fromY, toX, toY);
3956         }
3957     }
3958     
3959     /* Start the clocks */
3960     whiteFlag = blackFlag = FALSE;
3961     appData.clockMode = !(basetime == 0 && increment == 0);
3962     if (ticking == 0) {
3963       ics_clock_paused = TRUE;
3964       StopClocks();
3965     } else if (ticking == 1) {
3966       ics_clock_paused = FALSE;
3967     }
3968     if (gameMode == IcsIdle ||
3969         relation == RELATION_OBSERVING_STATIC ||
3970         relation == RELATION_EXAMINING ||
3971         ics_clock_paused)
3972       DisplayBothClocks();
3973     else
3974       StartClocks();
3975     
3976     /* Display opponents and material strengths */
3977     if (gameInfo.variant != VariantBughouse &&
3978         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3979         if (tinyLayout || smallLayout) {
3980             if(gameInfo.variant == VariantNormal)
3981                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3982                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3983                     basetime, increment);
3984             else
3985                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3986                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3987                     basetime, increment, (int) gameInfo.variant);
3988         } else {
3989             if(gameInfo.variant == VariantNormal)
3990                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3991                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3992                     basetime, increment);
3993             else
3994                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3995                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3996                     basetime, increment, VariantName(gameInfo.variant));
3997         }
3998         DisplayTitle(str);
3999   if (appData.debugMode) {
4000     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4001   }
4002     }
4003
4004    
4005     /* Display the board */
4006     if (!pausing && !appData.noGUI) {
4007       
4008       if (appData.premove)
4009           if (!gotPremove || 
4010              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4011              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4012               ClearPremoveHighlights();
4013
4014       DrawPosition(FALSE, boards[currentMove]);
4015       DisplayMove(moveNum - 1);
4016       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4017             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4018               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4019         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4020       }
4021     }
4022
4023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4024 #if ZIPPY
4025     if(bookHit) { // [HGM] book: simulate book reply
4026         static char bookMove[MSG_SIZ]; // a bit generous?
4027
4028         programStats.nodes = programStats.depth = programStats.time = 
4029         programStats.score = programStats.got_only_move = 0;
4030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4031
4032         strcpy(bookMove, "move ");
4033         strcat(bookMove, bookHit);
4034         HandleMachineMove(bookMove, &first);
4035     }
4036 #endif
4037 }
4038
4039 void
4040 GetMoveListEvent()
4041 {
4042     char buf[MSG_SIZ];
4043     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4044         ics_getting_history = H_REQUESTED;
4045         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4046         SendToICS(buf);
4047     }
4048 }
4049
4050 void
4051 AnalysisPeriodicEvent(force)
4052      int force;
4053 {
4054     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4055          && !force) || !appData.periodicUpdates)
4056       return;
4057
4058     /* Send . command to Crafty to collect stats */
4059     SendToProgram(".\n", &first);
4060
4061     /* Don't send another until we get a response (this makes
4062        us stop sending to old Crafty's which don't understand
4063        the "." command (sending illegal cmds resets node count & time,
4064        which looks bad)) */
4065     programStats.ok_to_send = 0;
4066 }
4067
4068 void ics_update_width(new_width)
4069         int new_width;
4070 {
4071         ics_printf("set width %d\n", new_width);
4072 }
4073
4074 void
4075 SendMoveToProgram(moveNum, cps)
4076      int moveNum;
4077      ChessProgramState *cps;
4078 {
4079     char buf[MSG_SIZ];
4080
4081     if (cps->useUsermove) {
4082       SendToProgram("usermove ", cps);
4083     }
4084     if (cps->useSAN) {
4085       char *space;
4086       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4087         int len = space - parseList[moveNum];
4088         memcpy(buf, parseList[moveNum], len);
4089         buf[len++] = '\n';
4090         buf[len] = NULLCHAR;
4091       } else {
4092         sprintf(buf, "%s\n", parseList[moveNum]);
4093       }
4094       SendToProgram(buf, cps);
4095     } else {
4096       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4097         AlphaRank(moveList[moveNum], 4);
4098         SendToProgram(moveList[moveNum], cps);
4099         AlphaRank(moveList[moveNum], 4); // and back
4100       } else
4101       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4102        * the engine. It would be nice to have a better way to identify castle 
4103        * moves here. */
4104       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4105                                                                          && cps->useOOCastle) {
4106         int fromX = moveList[moveNum][0] - AAA; 
4107         int fromY = moveList[moveNum][1] - ONE;
4108         int toX = moveList[moveNum][2] - AAA; 
4109         int toY = moveList[moveNum][3] - ONE;
4110         if((boards[moveNum][fromY][fromX] == WhiteKing 
4111             && boards[moveNum][toY][toX] == WhiteRook)
4112            || (boards[moveNum][fromY][fromX] == BlackKing 
4113                && boards[moveNum][toY][toX] == BlackRook)) {
4114           if(toX > fromX) SendToProgram("O-O\n", cps);
4115           else SendToProgram("O-O-O\n", cps);
4116         }
4117         else SendToProgram(moveList[moveNum], cps);
4118       }
4119       else SendToProgram(moveList[moveNum], cps);
4120       /* End of additions by Tord */
4121     }
4122
4123     /* [HGM] setting up the opening has brought engine in force mode! */
4124     /*       Send 'go' if we are in a mode where machine should play. */
4125     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4126         (gameMode == TwoMachinesPlay   ||
4127 #ifdef ZIPPY
4128          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4129 #endif
4130          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4131         SendToProgram("go\n", cps);
4132   if (appData.debugMode) {
4133     fprintf(debugFP, "(extra)\n");
4134   }
4135     }
4136     setboardSpoiledMachineBlack = 0;
4137 }
4138
4139 void
4140 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4141      ChessMove moveType;
4142      int fromX, fromY, toX, toY;
4143 {
4144     char user_move[MSG_SIZ];
4145
4146     switch (moveType) {
4147       default:
4148         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4149                 (int)moveType, fromX, fromY, toX, toY);
4150         DisplayError(user_move + strlen("say "), 0);
4151         break;
4152       case WhiteKingSideCastle:
4153       case BlackKingSideCastle:
4154       case WhiteQueenSideCastleWild:
4155       case BlackQueenSideCastleWild:
4156       /* PUSH Fabien */
4157       case WhiteHSideCastleFR:
4158       case BlackHSideCastleFR:
4159       /* POP Fabien */
4160         sprintf(user_move, "o-o\n");
4161         break;
4162       case WhiteQueenSideCastle:
4163       case BlackQueenSideCastle:
4164       case WhiteKingSideCastleWild:
4165       case BlackKingSideCastleWild:
4166       /* PUSH Fabien */
4167       case WhiteASideCastleFR:
4168       case BlackASideCastleFR:
4169       /* POP Fabien */
4170         sprintf(user_move, "o-o-o\n");
4171         break;
4172       case WhitePromotionQueen:
4173       case BlackPromotionQueen:
4174       case WhitePromotionRook:
4175       case BlackPromotionRook:
4176       case WhitePromotionBishop:
4177       case BlackPromotionBishop:
4178       case WhitePromotionKnight:
4179       case BlackPromotionKnight:
4180       case WhitePromotionKing:
4181       case BlackPromotionKing:
4182       case WhitePromotionChancellor:
4183       case BlackPromotionChancellor:
4184       case WhitePromotionArchbishop:
4185       case BlackPromotionArchbishop:
4186         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4187             sprintf(user_move, "%c%c%c%c=%c\n",
4188                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189                 PieceToChar(WhiteFerz));
4190         else if(gameInfo.variant == VariantGreat)
4191             sprintf(user_move, "%c%c%c%c=%c\n",
4192                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193                 PieceToChar(WhiteMan));
4194         else
4195             sprintf(user_move, "%c%c%c%c=%c\n",
4196                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4197                 PieceToChar(PromoPiece(moveType)));
4198         break;
4199       case WhiteDrop:
4200       case BlackDrop:
4201         sprintf(user_move, "%c@%c%c\n",
4202                 ToUpper(PieceToChar((ChessSquare) fromX)),
4203                 AAA + toX, ONE + toY);
4204         break;
4205       case NormalMove:
4206       case WhiteCapturesEnPassant:
4207       case BlackCapturesEnPassant:
4208       case IllegalMove:  /* could be a variant we don't quite understand */
4209         sprintf(user_move, "%c%c%c%c\n",
4210                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4211         break;
4212     }
4213     SendToICS(user_move);
4214     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4215         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4216 }
4217
4218 void
4219 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4220      int rf, ff, rt, ft;
4221      char promoChar;
4222      char move[7];
4223 {
4224     if (rf == DROP_RANK) {
4225         sprintf(move, "%c@%c%c\n",
4226                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4227     } else {
4228         if (promoChar == 'x' || promoChar == NULLCHAR) {
4229             sprintf(move, "%c%c%c%c\n",
4230                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4231         } else {
4232             sprintf(move, "%c%c%c%c%c\n",
4233                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4234         }
4235     }
4236 }
4237
4238 void
4239 ProcessICSInitScript(f)
4240      FILE *f;
4241 {
4242     char buf[MSG_SIZ];
4243
4244     while (fgets(buf, MSG_SIZ, f)) {
4245         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4246     }
4247
4248     fclose(f);
4249 }
4250
4251
4252 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4253 void
4254 AlphaRank(char *move, int n)
4255 {
4256 //    char *p = move, c; int x, y;
4257
4258     if (appData.debugMode) {
4259         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4260     }
4261
4262     if(move[1]=='*' && 
4263        move[2]>='0' && move[2]<='9' &&
4264        move[3]>='a' && move[3]<='x'    ) {
4265         move[1] = '@';
4266         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4267         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4268     } else
4269     if(move[0]>='0' && move[0]<='9' &&
4270        move[1]>='a' && move[1]<='x' &&
4271        move[2]>='0' && move[2]<='9' &&
4272        move[3]>='a' && move[3]<='x'    ) {
4273         /* input move, Shogi -> normal */
4274         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4275         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4276         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4277         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4278     } else
4279     if(move[1]=='@' &&
4280        move[3]>='0' && move[3]<='9' &&
4281        move[2]>='a' && move[2]<='x'    ) {
4282         move[1] = '*';
4283         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4284         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4285     } else
4286     if(
4287        move[0]>='a' && move[0]<='x' &&
4288        move[3]>='0' && move[3]<='9' &&
4289        move[2]>='a' && move[2]<='x'    ) {
4290          /* output move, normal -> Shogi */
4291         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4292         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4293         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4294         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4295         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4296     }
4297     if (appData.debugMode) {
4298         fprintf(debugFP, "   out = '%s'\n", move);
4299     }
4300 }
4301
4302 /* Parser for moves from gnuchess, ICS, or user typein box */
4303 Boolean
4304 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4305      char *move;
4306      int moveNum;
4307      ChessMove *moveType;
4308      int *fromX, *fromY, *toX, *toY;
4309      char *promoChar;
4310 {       
4311     if (appData.debugMode) {
4312         fprintf(debugFP, "move to parse: %s\n", move);
4313     }
4314     *moveType = yylexstr(moveNum, move);
4315
4316     switch (*moveType) {
4317       case WhitePromotionChancellor:
4318       case BlackPromotionChancellor:
4319       case WhitePromotionArchbishop:
4320       case BlackPromotionArchbishop:
4321       case WhitePromotionQueen:
4322       case BlackPromotionQueen:
4323       case WhitePromotionRook:
4324       case BlackPromotionRook:
4325       case WhitePromotionBishop:
4326       case BlackPromotionBishop:
4327       case WhitePromotionKnight:
4328       case BlackPromotionKnight:
4329       case WhitePromotionKing:
4330       case BlackPromotionKing:
4331       case NormalMove:
4332       case WhiteCapturesEnPassant:
4333       case BlackCapturesEnPassant:
4334       case WhiteKingSideCastle:
4335       case WhiteQueenSideCastle:
4336       case BlackKingSideCastle:
4337       case BlackQueenSideCastle:
4338       case WhiteKingSideCastleWild:
4339       case WhiteQueenSideCastleWild:
4340       case BlackKingSideCastleWild:
4341       case BlackQueenSideCastleWild:
4342       /* Code added by Tord: */
4343       case WhiteHSideCastleFR:
4344       case WhiteASideCastleFR:
4345       case BlackHSideCastleFR:
4346       case BlackASideCastleFR:
4347       /* End of code added by Tord */
4348       case IllegalMove:         /* bug or odd chess variant */
4349         *fromX = currentMoveString[0] - AAA;
4350         *fromY = currentMoveString[1] - ONE;
4351         *toX = currentMoveString[2] - AAA;
4352         *toY = currentMoveString[3] - ONE;
4353         *promoChar = currentMoveString[4];
4354         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4355             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4356     if (appData.debugMode) {
4357         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4358     }
4359             *fromX = *fromY = *toX = *toY = 0;
4360             return FALSE;
4361         }
4362         if (appData.testLegality) {
4363           return (*moveType != IllegalMove);
4364         } else {
4365           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4366                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4367         }
4368
4369       case WhiteDrop:
4370       case BlackDrop:
4371         *fromX = *moveType == WhiteDrop ?
4372           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4373           (int) CharToPiece(ToLower(currentMoveString[0]));
4374         *fromY = DROP_RANK;
4375         *toX = currentMoveString[2] - AAA;
4376         *toY = currentMoveString[3] - ONE;
4377         *promoChar = NULLCHAR;
4378         return TRUE;
4379
4380       case AmbiguousMove:
4381       case ImpossibleMove:
4382       case (ChessMove) 0:       /* end of file */
4383       case ElapsedTime:
4384       case Comment:
4385       case PGNTag:
4386       case NAG:
4387       case WhiteWins:
4388       case BlackWins:
4389       case GameIsDrawn:
4390       default:
4391     if (appData.debugMode) {
4392         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4393     }
4394         /* bug? */
4395         *fromX = *fromY = *toX = *toY = 0;
4396         *promoChar = NULLCHAR;
4397         return FALSE;
4398     }
4399 }
4400
4401
4402 void
4403 ParsePV(char *pv)
4404 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4405   int fromX, fromY, toX, toY; char promoChar;
4406   ChessMove moveType;
4407   Boolean valid;
4408   int nr = 0;
4409
4410   endPV = forwardMostMove;
4411   do {
4412     while(*pv == ' ') pv++;
4413     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4414     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4415 if(appData.debugMode){
4416 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4417 }
4418     if(!valid && nr == 0 &&
4419        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4420         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4421     }
4422     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4423     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4424     nr++;
4425     if(endPV+1 > framePtr) break; // no space, truncate
4426     if(!valid) break;
4427     endPV++;
4428     CopyBoard(boards[endPV], boards[endPV-1]);
4429     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4430     moveList[endPV-1][0] = fromX + AAA;
4431     moveList[endPV-1][1] = fromY + ONE;
4432     moveList[endPV-1][2] = toX + AAA;
4433     moveList[endPV-1][3] = toY + ONE;
4434     parseList[endPV-1][0] = NULLCHAR;
4435   } while(valid);
4436   currentMove = endPV;
4437   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4438   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4439                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4440   DrawPosition(TRUE, boards[currentMove]);
4441 }
4442
4443 static int lastX, lastY;
4444
4445 Boolean
4446 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4447 {
4448         int startPV;
4449
4450         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4451         lastX = x; lastY = y;
4452         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4453         startPV = index;
4454       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4455       index = startPV;
4456         while(buf[index] && buf[index] != '\n') index++;
4457         buf[index] = 0;
4458         ParsePV(buf+startPV);
4459         *start = startPV; *end = index-1;
4460         return TRUE;
4461 }
4462
4463 Boolean
4464 LoadPV(int x, int y)
4465 { // called on right mouse click to load PV
4466   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4467   lastX = x; lastY = y;
4468   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4469   return TRUE;
4470 }
4471
4472 void
4473 UnLoadPV()
4474 {
4475   if(endPV < 0) return;
4476   endPV = -1;
4477   currentMove = forwardMostMove;
4478   ClearPremoveHighlights();
4479   DrawPosition(TRUE, boards[currentMove]);
4480 }
4481
4482 void
4483 MovePV(int x, int y, int h)
4484 { // step through PV based on mouse coordinates (called on mouse move)
4485   int margin = h>>3, step = 0;
4486
4487   if(endPV < 0) return;
4488   // we must somehow check if right button is still down (might be released off board!)
4489   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4490   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4491   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4492   if(!step) return;
4493   lastX = x; lastY = y;
4494   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4495   currentMove += step;
4496   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4497   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4498                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4499   DrawPosition(FALSE, boards[currentMove]);
4500 }
4501
4502
4503 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4504 // All positions will have equal probability, but the current method will not provide a unique
4505 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4506 #define DARK 1
4507 #define LITE 2
4508 #define ANY 3
4509
4510 int squaresLeft[4];
4511 int piecesLeft[(int)BlackPawn];
4512 int seed, nrOfShuffles;
4513
4514 void GetPositionNumber()
4515 {       // sets global variable seed
4516         int i;
4517
4518         seed = appData.defaultFrcPosition;
4519         if(seed < 0) { // randomize based on time for negative FRC position numbers
4520                 for(i=0; i<50; i++) seed += random();
4521                 seed = random() ^ random() >> 8 ^ random() << 8;
4522                 if(seed<0) seed = -seed;
4523         }
4524 }
4525
4526 int put(Board board, int pieceType, int rank, int n, int shade)
4527 // put the piece on the (n-1)-th empty squares of the given shade
4528 {
4529         int i;
4530
4531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4532                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4533                         board[rank][i] = (ChessSquare) pieceType;
4534                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4535                         squaresLeft[ANY]--;
4536                         piecesLeft[pieceType]--; 
4537                         return i;
4538                 }
4539         }
4540         return -1;
4541 }
4542
4543
4544 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4545 // calculate where the next piece goes, (any empty square), and put it there
4546 {
4547         int i;
4548
4549         i = seed % squaresLeft[shade];
4550         nrOfShuffles *= squaresLeft[shade];
4551         seed /= squaresLeft[shade];
4552         put(board, pieceType, rank, i, shade);
4553 }
4554
4555 void AddTwoPieces(Board board, int pieceType, int rank)
4556 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4557 {
4558         int i, n=squaresLeft[ANY], j=n-1, k;
4559
4560         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4561         i = seed % k;  // pick one
4562         nrOfShuffles *= k;
4563         seed /= k;
4564         while(i >= j) i -= j--;
4565         j = n - 1 - j; i += j;
4566         put(board, pieceType, rank, j, ANY);
4567         put(board, pieceType, rank, i, ANY);
4568 }
4569
4570 void SetUpShuffle(Board board, int number)
4571 {
4572         int i, p, first=1;
4573
4574         GetPositionNumber(); nrOfShuffles = 1;
4575
4576         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4577         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4578         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4579
4580         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4581
4582         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4583             p = (int) board[0][i];
4584             if(p < (int) BlackPawn) piecesLeft[p] ++;
4585             board[0][i] = EmptySquare;
4586         }
4587
4588         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4589             // shuffles restricted to allow normal castling put KRR first
4590             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4591                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4592             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4593                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4594             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4595                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4596             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4597                 put(board, WhiteRook, 0, 0, ANY);
4598             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4599         }
4600
4601         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4602             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4603             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4604                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4605                 while(piecesLeft[p] >= 2) {
4606                     AddOnePiece(board, p, 0, LITE);
4607                     AddOnePiece(board, p, 0, DARK);
4608                 }
4609                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4610             }
4611
4612         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4613             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4614             // but we leave King and Rooks for last, to possibly obey FRC restriction
4615             if(p == (int)WhiteRook) continue;
4616             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4617             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4618         }
4619
4620         // now everything is placed, except perhaps King (Unicorn) and Rooks
4621
4622         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4623             // Last King gets castling rights
4624             while(piecesLeft[(int)WhiteUnicorn]) {
4625                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4626                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4627             }
4628
4629             while(piecesLeft[(int)WhiteKing]) {
4630                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4631                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4632             }
4633
4634
4635         } else {
4636             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4637             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4638         }
4639
4640         // Only Rooks can be left; simply place them all
4641         while(piecesLeft[(int)WhiteRook]) {
4642                 i = put(board, WhiteRook, 0, 0, ANY);
4643                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4644                         if(first) {
4645                                 first=0;
4646                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4647                         }
4648                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4649                 }
4650         }
4651         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4652             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4653         }
4654
4655         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4656 }
4657
4658 int SetCharTable( char *table, const char * map )
4659 /* [HGM] moved here from winboard.c because of its general usefulness */
4660 /*       Basically a safe strcpy that uses the last character as King */
4661 {
4662     int result = FALSE; int NrPieces;
4663
4664     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4665                     && NrPieces >= 12 && !(NrPieces&1)) {
4666         int i; /* [HGM] Accept even length from 12 to 34 */
4667
4668         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4669         for( i=0; i<NrPieces/2-1; i++ ) {
4670             table[i] = map[i];
4671             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4672         }
4673         table[(int) WhiteKing]  = map[NrPieces/2-1];
4674         table[(int) BlackKing]  = map[NrPieces-1];
4675
4676         result = TRUE;
4677     }
4678
4679     return result;
4680 }
4681
4682 void Prelude(Board board)
4683 {       // [HGM] superchess: random selection of exo-pieces
4684         int i, j, k; ChessSquare p; 
4685         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4686
4687         GetPositionNumber(); // use FRC position number
4688
4689         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4690             SetCharTable(pieceToChar, appData.pieceToCharTable);
4691             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4692                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4693         }
4694
4695         j = seed%4;                 seed /= 4; 
4696         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4697         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4698         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4699         j = seed%3 + (seed%3 >= j); seed /= 3; 
4700         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4701         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4702         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4703         j = seed%3;                 seed /= 3; 
4704         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4707         j = seed%2 + (seed%2 >= j); seed /= 2; 
4708         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4711         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4712         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4713         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4714         put(board, exoPieces[0],    0, 0, ANY);
4715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4716 }
4717
4718 void
4719 InitPosition(redraw)
4720      int redraw;
4721 {
4722     ChessSquare (* pieces)[BOARD_FILES];
4723     int i, j, pawnRow, overrule,
4724     oldx = gameInfo.boardWidth,
4725     oldy = gameInfo.boardHeight,
4726     oldh = gameInfo.holdingsWidth,
4727     oldv = gameInfo.variant;
4728
4729     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4730
4731     /* [AS] Initialize pv info list [HGM] and game status */
4732     {
4733         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4734             pvInfoList[i].depth = 0;
4735             boards[i][EP_STATUS] = EP_NONE;
4736             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4737         }
4738
4739         initialRulePlies = 0; /* 50-move counter start */
4740
4741         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4742         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4743     }
4744
4745     
4746     /* [HGM] logic here is completely changed. In stead of full positions */
4747     /* the initialized data only consist of the two backranks. The switch */
4748     /* selects which one we will use, which is than copied to the Board   */
4749     /* initialPosition, which for the rest is initialized by Pawns and    */
4750     /* empty squares. This initial position is then copied to boards[0],  */
4751     /* possibly after shuffling, so that it remains available.            */
4752
4753     gameInfo.holdingsWidth = 0; /* default board sizes */
4754     gameInfo.boardWidth    = 8;
4755     gameInfo.boardHeight   = 8;
4756     gameInfo.holdingsSize  = 0;
4757     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4758     for(i=0; i<BOARD_FILES-2; i++)
4759       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4760     initialPosition[EP_STATUS] = EP_NONE;
4761     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4762
4763     switch (gameInfo.variant) {
4764     case VariantFischeRandom:
4765       shuffleOpenings = TRUE;
4766     default:
4767       pieces = FIDEArray;
4768       break;
4769     case VariantShatranj:
4770       pieces = ShatranjArray;
4771       nrCastlingRights = 0;
4772       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4773       break;
4774     case VariantTwoKings:
4775       pieces = twoKingsArray;
4776       break;
4777     case VariantCapaRandom:
4778       shuffleOpenings = TRUE;
4779     case VariantCapablanca:
4780       pieces = CapablancaArray;
4781       gameInfo.boardWidth = 10;
4782       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4783       break;
4784     case VariantGothic:
4785       pieces = GothicArray;
4786       gameInfo.boardWidth = 10;
4787       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4788       break;
4789     case VariantJanus:
4790       pieces = JanusArray;
4791       gameInfo.boardWidth = 10;
4792       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4793       nrCastlingRights = 6;
4794         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4795         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4796         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4797         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4798         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4799         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4800       break;
4801     case VariantFalcon:
4802       pieces = FalconArray;
4803       gameInfo.boardWidth = 10;
4804       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4805       break;
4806     case VariantXiangqi:
4807       pieces = XiangqiArray;
4808       gameInfo.boardWidth  = 9;
4809       gameInfo.boardHeight = 10;
4810       nrCastlingRights = 0;
4811       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4812       break;
4813     case VariantShogi:
4814       pieces = ShogiArray;
4815       gameInfo.boardWidth  = 9;
4816       gameInfo.boardHeight = 9;
4817       gameInfo.holdingsSize = 7;
4818       nrCastlingRights = 0;
4819       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4820       break;
4821     case VariantCourier:
4822       pieces = CourierArray;
4823       gameInfo.boardWidth  = 12;
4824       nrCastlingRights = 0;
4825       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4826       break;
4827     case VariantKnightmate:
4828       pieces = KnightmateArray;
4829       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4830       break;
4831     case VariantFairy:
4832       pieces = fairyArray;
4833       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4834       break;
4835     case VariantGreat:
4836       pieces = GreatArray;
4837       gameInfo.boardWidth = 10;
4838       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4839       gameInfo.holdingsSize = 8;
4840       break;
4841     case VariantSuper:
4842       pieces = FIDEArray;
4843       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4844       gameInfo.holdingsSize = 8;
4845       startedFromSetupPosition = TRUE;
4846       break;
4847     case VariantCrazyhouse:
4848     case VariantBughouse:
4849       pieces = FIDEArray;
4850       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4851       gameInfo.holdingsSize = 5;
4852       break;
4853     case VariantWildCastle:
4854       pieces = FIDEArray;
4855       /* !!?shuffle with kings guaranteed to be on d or e file */
4856       shuffleOpenings = 1;
4857       break;
4858     case VariantNoCastle:
4859       pieces = FIDEArray;
4860       nrCastlingRights = 0;
4861       /* !!?unconstrained back-rank shuffle */
4862       shuffleOpenings = 1;
4863       break;
4864     }
4865
4866     overrule = 0;
4867     if(appData.NrFiles >= 0) {
4868         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4869         gameInfo.boardWidth = appData.NrFiles;
4870     }
4871     if(appData.NrRanks >= 0) {
4872         gameInfo.boardHeight = appData.NrRanks;
4873     }
4874     if(appData.holdingsSize >= 0) {
4875         i = appData.holdingsSize;
4876         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4877         gameInfo.holdingsSize = i;
4878     }
4879     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4880     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4881         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4882
4883     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4884     if(pawnRow < 1) pawnRow = 1;
4885
4886     /* User pieceToChar list overrules defaults */
4887     if(appData.pieceToCharTable != NULL)
4888         SetCharTable(pieceToChar, appData.pieceToCharTable);
4889
4890     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4891
4892         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4893             s = (ChessSquare) 0; /* account holding counts in guard band */
4894         for( i=0; i<BOARD_HEIGHT; i++ )
4895             initialPosition[i][j] = s;
4896
4897         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4898         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4899         initialPosition[pawnRow][j] = WhitePawn;
4900         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4901         if(gameInfo.variant == VariantXiangqi) {
4902             if(j&1) {
4903                 initialPosition[pawnRow][j] = 
4904                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4905                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4906                    initialPosition[2][j] = WhiteCannon;
4907                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4908                 }
4909             }
4910         }
4911         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4912     }
4913     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4914
4915             j=BOARD_LEFT+1;
4916             initialPosition[1][j] = WhiteBishop;
4917             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4918             j=BOARD_RGHT-2;
4919             initialPosition[1][j] = WhiteRook;
4920             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4921     }
4922
4923     if( nrCastlingRights == -1) {
4924         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4925         /*       This sets default castling rights from none to normal corners   */
4926         /* Variants with other castling rights must set them themselves above    */
4927         nrCastlingRights = 6;
4928        
4929         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4930         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4931         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4932         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4933         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4934         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4935      }
4936
4937      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4938      if(gameInfo.variant == VariantGreat) { // promotion commoners
4939         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4940         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4941         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4942         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4943      }
4944   if (appData.debugMode) {
4945     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4946   }
4947     if(shuffleOpenings) {
4948         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4949         startedFromSetupPosition = TRUE;
4950     }
4951     if(startedFromPositionFile) {
4952       /* [HGM] loadPos: use PositionFile for every new game */
4953       CopyBoard(initialPosition, filePosition);
4954       for(i=0; i<nrCastlingRights; i++)
4955           initialRights[i] = filePosition[CASTLING][i];
4956       startedFromSetupPosition = TRUE;
4957     }
4958
4959     CopyBoard(boards[0], initialPosition);
4960
4961     if(oldx != gameInfo.boardWidth ||
4962        oldy != gameInfo.boardHeight ||
4963        oldh != gameInfo.holdingsWidth
4964 #ifdef GOTHIC
4965        || oldv == VariantGothic ||        // For licensing popups
4966        gameInfo.variant == VariantGothic
4967 #endif
4968 #ifdef FALCON
4969        || oldv == VariantFalcon ||
4970        gameInfo.variant == VariantFalcon
4971 #endif
4972                                          )
4973             InitDrawingSizes(-2 ,0);
4974
4975     if (redraw)
4976       DrawPosition(TRUE, boards[currentMove]);
4977 }
4978
4979 void
4980 SendBoard(cps, moveNum)
4981      ChessProgramState *cps;
4982      int moveNum;
4983 {
4984     char message[MSG_SIZ];
4985     
4986     if (cps->useSetboard) {
4987       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4988       sprintf(message, "setboard %s\n", fen);
4989       SendToProgram(message, cps);
4990       free(fen);
4991
4992     } else {
4993       ChessSquare *bp;
4994       int i, j;
4995       /* Kludge to set black to move, avoiding the troublesome and now
4996        * deprecated "black" command.
4997        */
4998       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4999
5000       SendToProgram("edit\n", cps);
5001       SendToProgram("#\n", cps);
5002       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5003         bp = &boards[moveNum][i][BOARD_LEFT];
5004         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5005           if ((int) *bp < (int) BlackPawn) {
5006             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5007                     AAA + j, ONE + i);
5008             if(message[0] == '+' || message[0] == '~') {
5009                 sprintf(message, "%c%c%c+\n",
5010                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5011                         AAA + j, ONE + i);
5012             }
5013             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5014                 message[1] = BOARD_RGHT   - 1 - j + '1';
5015                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5016             }
5017             SendToProgram(message, cps);
5018           }
5019         }
5020       }
5021     
5022       SendToProgram("c\n", cps);
5023       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5024         bp = &boards[moveNum][i][BOARD_LEFT];
5025         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5026           if (((int) *bp != (int) EmptySquare)
5027               && ((int) *bp >= (int) BlackPawn)) {
5028             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5029                     AAA + j, ONE + i);
5030             if(message[0] == '+' || message[0] == '~') {
5031                 sprintf(message, "%c%c%c+\n",
5032                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5033                         AAA + j, ONE + i);
5034             }
5035             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5036                 message[1] = BOARD_RGHT   - 1 - j + '1';
5037                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5038             }
5039             SendToProgram(message, cps);
5040           }
5041         }
5042       }
5043     
5044       SendToProgram(".\n", cps);
5045     }
5046     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5047 }
5048
5049 int
5050 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5051 {
5052     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5053     /* [HGM] add Shogi promotions */
5054     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5055     ChessSquare piece;
5056     ChessMove moveType;
5057     Boolean premove;
5058
5059     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5060     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5061
5062     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5063       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5064         return FALSE;
5065
5066     piece = boards[currentMove][fromY][fromX];
5067     if(gameInfo.variant == VariantShogi) {
5068         promotionZoneSize = 3;
5069         highestPromotingPiece = (int)WhiteFerz;
5070     }
5071
5072     // next weed out all moves that do not touch the promotion zone at all
5073     if((int)piece >= BlackPawn) {
5074         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5075              return FALSE;
5076         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5077     } else {
5078         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5079            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5080     }
5081
5082     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5083
5084     // weed out mandatory Shogi promotions
5085     if(gameInfo.variant == VariantShogi) {
5086         if(piece >= BlackPawn) {
5087             if(toY == 0 && piece == BlackPawn ||
5088                toY == 0 && piece == BlackQueen ||
5089                toY <= 1 && piece == BlackKnight) {
5090                 *promoChoice = '+';
5091                 return FALSE;
5092             }
5093         } else {
5094             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5095                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5096                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5097                 *promoChoice = '+';
5098                 return FALSE;
5099             }
5100         }
5101     }
5102
5103     // weed out obviously illegal Pawn moves
5104     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5105         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5106         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5107         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5108         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5109         // note we are not allowed to test for valid (non-)capture, due to premove
5110     }
5111
5112     // we either have a choice what to promote to, or (in Shogi) whether to promote
5113     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5114         *promoChoice = PieceToChar(BlackFerz);  // no choice
5115         return FALSE;
5116     }
5117     if(appData.alwaysPromoteToQueen) { // predetermined
5118         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5119              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5120         else *promoChoice = PieceToChar(BlackQueen);
5121         return FALSE;
5122     }
5123
5124     // suppress promotion popup on illegal moves that are not premoves
5125     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5126               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5127     if(appData.testLegality && !premove) {
5128         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5129                         fromY, fromX, toY, toX, NULLCHAR);
5130         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5131            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5132             return FALSE;
5133     }
5134
5135     return TRUE;
5136 }
5137
5138 int
5139 InPalace(row, column)
5140      int row, column;
5141 {   /* [HGM] for Xiangqi */
5142     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5143          column < (BOARD_WIDTH + 4)/2 &&
5144          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5145     return FALSE;
5146 }
5147
5148 int
5149 PieceForSquare (x, y)
5150      int x;
5151      int y;
5152 {
5153   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5154      return -1;
5155   else
5156      return boards[currentMove][y][x];
5157 }
5158
5159 int
5160 OKToStartUserMove(x, y)
5161      int x, y;
5162 {
5163     ChessSquare from_piece;
5164     int white_piece;
5165
5166     if (matchMode) return FALSE;
5167     if (gameMode == EditPosition) return TRUE;
5168
5169     if (x >= 0 && y >= 0)
5170       from_piece = boards[currentMove][y][x];
5171     else
5172       from_piece = EmptySquare;
5173
5174     if (from_piece == EmptySquare) return FALSE;
5175
5176     white_piece = (int)from_piece >= (int)WhitePawn &&
5177       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5178
5179     switch (gameMode) {
5180       case PlayFromGameFile:
5181       case AnalyzeFile:
5182       case TwoMachinesPlay:
5183       case EndOfGame:
5184         return FALSE;
5185
5186       case IcsObserving:
5187       case IcsIdle:
5188         return FALSE;
5189
5190       case MachinePlaysWhite:
5191       case IcsPlayingBlack:
5192         if (appData.zippyPlay) return FALSE;
5193         if (white_piece) {
5194             DisplayMoveError(_("You are playing Black"));
5195             return FALSE;
5196         }
5197         break;
5198
5199       case MachinePlaysBlack:
5200       case IcsPlayingWhite:
5201         if (appData.zippyPlay) return FALSE;
5202         if (!white_piece) {
5203             DisplayMoveError(_("You are playing White"));
5204             return FALSE;
5205         }
5206         break;
5207
5208       case EditGame:
5209         if (!white_piece && WhiteOnMove(currentMove)) {
5210             DisplayMoveError(_("It is White's turn"));
5211             return FALSE;
5212         }           
5213         if (white_piece && !WhiteOnMove(currentMove)) {
5214             DisplayMoveError(_("It is Black's turn"));
5215             return FALSE;
5216         }           
5217         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5218             /* Editing correspondence game history */
5219             /* Could disallow this or prompt for confirmation */
5220             cmailOldMove = -1;
5221         }
5222         break;
5223
5224       case BeginningOfGame:
5225         if (appData.icsActive) return FALSE;
5226         if (!appData.noChessProgram) {
5227             if (!white_piece) {
5228                 DisplayMoveError(_("You are playing White"));
5229                 return FALSE;
5230             }
5231         }
5232         break;
5233         
5234       case Training:
5235         if (!white_piece && WhiteOnMove(currentMove)) {
5236             DisplayMoveError(_("It is White's turn"));
5237             return FALSE;
5238         }           
5239         if (white_piece && !WhiteOnMove(currentMove)) {
5240             DisplayMoveError(_("It is Black's turn"));
5241             return FALSE;
5242         }           
5243         break;
5244
5245       default:
5246       case IcsExamining:
5247         break;
5248     }
5249     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5250         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5251         && gameMode != AnalyzeFile && gameMode != Training) {
5252         DisplayMoveError(_("Displayed position is not current"));
5253         return FALSE;
5254     }
5255     return TRUE;
5256 }
5257
5258 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5259 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5260 int lastLoadGameUseList = FALSE;
5261 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5262 ChessMove lastLoadGameStart = (ChessMove) 0;
5263
5264 ChessMove
5265 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5266      int fromX, fromY, toX, toY;
5267      int promoChar;
5268      Boolean captureOwn;
5269 {
5270     ChessMove moveType;
5271     ChessSquare pdown, pup;
5272
5273     /* Check if the user is playing in turn.  This is complicated because we
5274        let the user "pick up" a piece before it is his turn.  So the piece he
5275        tried to pick up may have been captured by the time he puts it down!
5276        Therefore we use the color the user is supposed to be playing in this
5277        test, not the color of the piece that is currently on the starting
5278        square---except in EditGame mode, where the user is playing both
5279        sides; fortunately there the capture race can't happen.  (It can
5280        now happen in IcsExamining mode, but that's just too bad.  The user
5281        will get a somewhat confusing message in that case.)
5282        */
5283
5284     switch (gameMode) {
5285       case PlayFromGameFile:
5286       case AnalyzeFile:
5287       case TwoMachinesPlay:
5288       case EndOfGame:
5289       case IcsObserving:
5290       case IcsIdle:
5291         /* We switched into a game mode where moves are not accepted,
5292            perhaps while the mouse button was down. */
5293         return ImpossibleMove;
5294
5295       case MachinePlaysWhite:
5296         /* User is moving for Black */
5297         if (WhiteOnMove(currentMove)) {
5298             DisplayMoveError(_("It is White's turn"));
5299             return ImpossibleMove;
5300         }
5301         break;
5302
5303       case MachinePlaysBlack:
5304         /* User is moving for White */
5305         if (!WhiteOnMove(currentMove)) {
5306             DisplayMoveError(_("It is Black's turn"));
5307             return ImpossibleMove;
5308         }
5309         break;
5310
5311       case EditGame:
5312       case IcsExamining:
5313       case BeginningOfGame:
5314       case AnalyzeMode:
5315       case Training:
5316         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5317             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5318             /* User is moving for Black */
5319             if (WhiteOnMove(currentMove)) {
5320                 DisplayMoveError(_("It is White's turn"));
5321                 return ImpossibleMove;
5322             }
5323         } else {
5324             /* User is moving for White */
5325             if (!WhiteOnMove(currentMove)) {
5326                 DisplayMoveError(_("It is Black's turn"));
5327                 return ImpossibleMove;
5328             }
5329         }
5330         break;
5331
5332       case IcsPlayingBlack:
5333         /* User is moving for Black */
5334         if (WhiteOnMove(currentMove)) {
5335             if (!appData.premove) {
5336                 DisplayMoveError(_("It is White's turn"));
5337             } else if (toX >= 0 && toY >= 0) {
5338                 premoveToX = toX;
5339                 premoveToY = toY;
5340                 premoveFromX = fromX;
5341                 premoveFromY = fromY;
5342                 premovePromoChar = promoChar;
5343                 gotPremove = 1;
5344                 if (appData.debugMode) 
5345                     fprintf(debugFP, "Got premove: fromX %d,"
5346                             "fromY %d, toX %d, toY %d\n",
5347                             fromX, fromY, toX, toY);
5348             }
5349             return ImpossibleMove;
5350         }
5351         break;
5352
5353       case IcsPlayingWhite:
5354         /* User is moving for White */
5355         if (!WhiteOnMove(currentMove)) {
5356             if (!appData.premove) {
5357                 DisplayMoveError(_("It is Black's turn"));
5358             } else if (toX >= 0 && toY >= 0) {
5359                 premoveToX = toX;
5360                 premoveToY = toY;
5361                 premoveFromX = fromX;
5362                 premoveFromY = fromY;
5363                 premovePromoChar = promoChar;
5364                 gotPremove = 1;
5365                 if (appData.debugMode) 
5366                     fprintf(debugFP, "Got premove: fromX %d,"
5367                             "fromY %d, toX %d, toY %d\n",
5368                             fromX, fromY, toX, toY);
5369             }
5370             return ImpossibleMove;
5371         }
5372         break;
5373
5374       default:
5375         break;
5376
5377       case EditPosition:
5378         /* EditPosition, empty square, or different color piece;
5379            click-click move is possible */
5380         if (toX == -2 || toY == -2) {
5381             boards[0][fromY][fromX] = EmptySquare;
5382             return AmbiguousMove;
5383         } else if (toX >= 0 && toY >= 0) {
5384             boards[0][toY][toX] = boards[0][fromY][fromX];
5385             boards[0][fromY][fromX] = EmptySquare;
5386             return AmbiguousMove;
5387         }
5388         return ImpossibleMove;
5389     }
5390
5391     if(toX < 0 || toY < 0) return ImpossibleMove;
5392     pdown = boards[currentMove][fromY][fromX];
5393     pup = boards[currentMove][toY][toX];
5394
5395     /* [HGM] If move started in holdings, it means a drop */
5396     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5397          if( pup != EmptySquare ) return ImpossibleMove;
5398          if(appData.testLegality) {
5399              /* it would be more logical if LegalityTest() also figured out
5400               * which drops are legal. For now we forbid pawns on back rank.
5401               * Shogi is on its own here...
5402               */
5403              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5404                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5405                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5406          }
5407          return WhiteDrop; /* Not needed to specify white or black yet */
5408     }
5409
5410     userOfferedDraw = FALSE;
5411         
5412     /* [HGM] always test for legality, to get promotion info */
5413     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5414                                          fromY, fromX, toY, toX, promoChar);
5415     /* [HGM] but possibly ignore an IllegalMove result */
5416     if (appData.testLegality) {
5417         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5418             DisplayMoveError(_("Illegal move"));
5419             return ImpossibleMove;
5420         }
5421     }
5422
5423     return moveType;
5424     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5425        function is made into one that returns an OK move type if FinishMove
5426        should be called. This to give the calling driver routine the
5427        opportunity to finish the userMove input with a promotion popup,
5428        without bothering the user with this for invalid or illegal moves */
5429
5430 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5431 }
5432
5433 /* Common tail of UserMoveEvent and DropMenuEvent */
5434 int
5435 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5436      ChessMove moveType;
5437      int fromX, fromY, toX, toY;
5438      /*char*/int promoChar;
5439 {
5440     char *bookHit = 0;
5441
5442     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5443         // [HGM] superchess: suppress promotions to non-available piece
5444         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5445         if(WhiteOnMove(currentMove)) {
5446             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5447         } else {
5448             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5449         }
5450     }
5451
5452     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5453        move type in caller when we know the move is a legal promotion */
5454     if(moveType == NormalMove && promoChar)
5455         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5456
5457     /* [HGM] convert drag-and-drop piece drops to standard form */
5458     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5459          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5460            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5461                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5462            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5463            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5464            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5465            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5466          fromY = DROP_RANK;
5467     }
5468
5469     /* [HGM] <popupFix> The following if has been moved here from
5470        UserMoveEvent(). Because it seemed to belong here (why not allow
5471        piece drops in training games?), and because it can only be
5472        performed after it is known to what we promote. */
5473     if (gameMode == Training) {
5474       /* compare the move played on the board to the next move in the
5475        * game. If they match, display the move and the opponent's response. 
5476        * If they don't match, display an error message.
5477        */
5478       int saveAnimate;
5479       Board testBoard;
5480       CopyBoard(testBoard, boards[currentMove]);
5481       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5482
5483       if (CompareBoards(testBoard, boards[currentMove+1])) {
5484         ForwardInner(currentMove+1);
5485
5486         /* Autoplay the opponent's response.
5487          * if appData.animate was TRUE when Training mode was entered,
5488          * the response will be animated.
5489          */
5490         saveAnimate = appData.animate;
5491         appData.animate = animateTraining;
5492         ForwardInner(currentMove+1);
5493         appData.animate = saveAnimate;
5494
5495         /* check for the end of the game */
5496         if (currentMove >= forwardMostMove) {
5497           gameMode = PlayFromGameFile;
5498           ModeHighlight();
5499           SetTrainingModeOff();
5500           DisplayInformation(_("End of game"));
5501         }
5502       } else {
5503         DisplayError(_("Incorrect move"), 0);
5504       }
5505       return 1;
5506     }
5507
5508   /* Ok, now we know that the move is good, so we can kill
5509      the previous line in Analysis Mode */
5510   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5511                                 && currentMove < forwardMostMove) {
5512     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5513   }
5514
5515   /* If we need the chess program but it's dead, restart it */
5516   ResurrectChessProgram();
5517
5518   /* A user move restarts a paused game*/
5519   if (pausing)
5520     PauseEvent();
5521
5522   thinkOutput[0] = NULLCHAR;
5523
5524   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5525
5526   if (gameMode == BeginningOfGame) {
5527     if (appData.noChessProgram) {
5528       gameMode = EditGame;
5529       SetGameInfo();
5530     } else {
5531       char buf[MSG_SIZ];
5532       gameMode = MachinePlaysBlack;
5533       StartClocks();
5534       SetGameInfo();
5535       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5536       DisplayTitle(buf);
5537       if (first.sendName) {
5538         sprintf(buf, "name %s\n", gameInfo.white);
5539         SendToProgram(buf, &first);
5540       }
5541       StartClocks();
5542     }
5543     ModeHighlight();
5544   }
5545
5546   /* Relay move to ICS or chess engine */
5547   if (appData.icsActive) {
5548     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5549         gameMode == IcsExamining) {
5550       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5551       ics_user_moved = 1;
5552     }
5553   } else {
5554     if (first.sendTime && (gameMode == BeginningOfGame ||
5555                            gameMode == MachinePlaysWhite ||
5556                            gameMode == MachinePlaysBlack)) {
5557       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5558     }
5559     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5560          // [HGM] book: if program might be playing, let it use book
5561         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5562         first.maybeThinking = TRUE;
5563     } else SendMoveToProgram(forwardMostMove-1, &first);
5564     if (currentMove == cmailOldMove + 1) {
5565       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5566     }
5567   }
5568
5569   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5570
5571   switch (gameMode) {
5572   case EditGame:
5573     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5574     case MT_NONE:
5575     case MT_CHECK:
5576       break;
5577     case MT_CHECKMATE:
5578     case MT_STAINMATE:
5579       if (WhiteOnMove(currentMove)) {
5580         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5581       } else {
5582         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5583       }
5584       break;
5585     case MT_STALEMATE:
5586       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5587       break;
5588     }
5589     break;
5590     
5591   case MachinePlaysBlack:
5592   case MachinePlaysWhite:
5593     /* disable certain menu options while machine is thinking */
5594     SetMachineThinkingEnables();
5595     break;
5596
5597   default:
5598     break;
5599   }
5600
5601   if(bookHit) { // [HGM] book: simulate book reply
5602         static char bookMove[MSG_SIZ]; // a bit generous?
5603
5604         programStats.nodes = programStats.depth = programStats.time = 
5605         programStats.score = programStats.got_only_move = 0;
5606         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5607
5608         strcpy(bookMove, "move ");
5609         strcat(bookMove, bookHit);
5610         HandleMachineMove(bookMove, &first);
5611   }
5612   return 1;
5613 }
5614
5615 void
5616 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5617      int fromX, fromY, toX, toY;
5618      int promoChar;
5619 {
5620     /* [HGM] This routine was added to allow calling of its two logical
5621        parts from other modules in the old way. Before, UserMoveEvent()
5622        automatically called FinishMove() if the move was OK, and returned
5623        otherwise. I separated the two, in order to make it possible to
5624        slip a promotion popup in between. But that it always needs two
5625        calls, to the first part, (now called UserMoveTest() ), and to
5626        FinishMove if the first part succeeded. Calls that do not need
5627        to do anything in between, can call this routine the old way. 
5628     */
5629     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5630 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5631     if(moveType == AmbiguousMove)
5632         DrawPosition(FALSE, boards[currentMove]);
5633     else if(moveType != ImpossibleMove && moveType != Comment)
5634         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5635 }
5636
5637 void LeftClick(ClickType clickType, int xPix, int yPix)
5638 {
5639     int x, y;
5640     Boolean saveAnimate;
5641     static int second = 0, promotionChoice = 0;
5642     char promoChoice = NULLCHAR;
5643
5644     if (clickType == Press) ErrorPopDown();
5645
5646     x = EventToSquare(xPix, BOARD_WIDTH);
5647     y = EventToSquare(yPix, BOARD_HEIGHT);
5648     if (!flipView && y >= 0) {
5649         y = BOARD_HEIGHT - 1 - y;
5650     }
5651     if (flipView && x >= 0) {
5652         x = BOARD_WIDTH - 1 - x;
5653     }
5654
5655     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5656         if(clickType == Release) return; // ignore upclick of click-click destination
5657         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5658         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5659         if(gameInfo.holdingsWidth && 
5660                 (WhiteOnMove(currentMove) 
5661                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5662                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5663             // click in right holdings, for determining promotion piece
5664             ChessSquare p = boards[currentMove][y][x];
5665             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5666             if(p != EmptySquare) {
5667                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5668                 fromX = fromY = -1;
5669                 return;
5670             }
5671         }
5672         DrawPosition(FALSE, boards[currentMove]);
5673         return;
5674     }
5675
5676     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5677     if(clickType == Press
5678             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5679               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5680               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5681         return;
5682
5683     if (fromX == -1) {
5684         if (clickType == Press) {
5685             /* First square */
5686             if (OKToStartUserMove(x, y)) {
5687                 fromX = x;
5688                 fromY = y;
5689                 second = 0;
5690                 DragPieceBegin(xPix, yPix);
5691                 if (appData.highlightDragging) {
5692                     SetHighlights(x, y, -1, -1);
5693                 }
5694             }
5695         }
5696         return;
5697     }
5698
5699     /* fromX != -1 */
5700     if (clickType == Press && gameMode != EditPosition) {
5701         ChessSquare fromP;
5702         ChessSquare toP;
5703         int frc;
5704
5705         // ignore off-board to clicks
5706         if(y < 0 || x < 0) return;
5707
5708         /* Check if clicking again on the same color piece */
5709         fromP = boards[currentMove][fromY][fromX];
5710         toP = boards[currentMove][y][x];
5711         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5712         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5713              WhitePawn <= toP && toP <= WhiteKing &&
5714              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5715              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5716             (BlackPawn <= fromP && fromP <= BlackKing && 
5717              BlackPawn <= toP && toP <= BlackKing &&
5718              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5719              !(fromP == BlackKing && toP == BlackRook && frc))) {
5720             /* Clicked again on same color piece -- changed his mind */
5721             second = (x == fromX && y == fromY);
5722             if (appData.highlightDragging) {
5723                 SetHighlights(x, y, -1, -1);
5724             } else {
5725                 ClearHighlights();
5726             }
5727             if (OKToStartUserMove(x, y)) {
5728                 fromX = x;
5729                 fromY = y;
5730                 DragPieceBegin(xPix, yPix);
5731             }
5732             return;
5733         }
5734         // ignore clicks on holdings
5735         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5736     }
5737
5738     if (clickType == Release && x == fromX && y == fromY) {
5739         DragPieceEnd(xPix, yPix);
5740         if (appData.animateDragging) {
5741             /* Undo animation damage if any */
5742             DrawPosition(FALSE, NULL);
5743         }
5744         if (second) {
5745             /* Second up/down in same square; just abort move */
5746             second = 0;
5747             fromX = fromY = -1;
5748             ClearHighlights();
5749             gotPremove = 0;
5750             ClearPremoveHighlights();
5751         } else {
5752             /* First upclick in same square; start click-click mode */
5753             SetHighlights(x, y, -1, -1);
5754         }
5755         return;
5756     }
5757
5758     /* we now have a different from- and (possibly off-board) to-square */
5759     /* Completed move */
5760     toX = x;
5761     toY = y;
5762     saveAnimate = appData.animate;
5763     if (clickType == Press) {
5764         /* Finish clickclick move */
5765         if (appData.animate || appData.highlightLastMove) {
5766             SetHighlights(fromX, fromY, toX, toY);
5767         } else {
5768             ClearHighlights();
5769         }
5770     } else {
5771         /* Finish drag move */
5772         if (appData.highlightLastMove) {
5773             SetHighlights(fromX, fromY, toX, toY);
5774         } else {
5775             ClearHighlights();
5776         }
5777         DragPieceEnd(xPix, yPix);
5778         /* Don't animate move and drag both */
5779         appData.animate = FALSE;
5780     }
5781
5782     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5783     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5784         ClearHighlights();
5785         fromX = fromY = -1;
5786         DrawPosition(TRUE, NULL);
5787         return;
5788     }
5789
5790     // off-board moves should not be highlighted
5791     if(x < 0 || x < 0) ClearHighlights();
5792
5793     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5794         SetHighlights(fromX, fromY, toX, toY);
5795         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5796             // [HGM] super: promotion to captured piece selected from holdings
5797             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5798             promotionChoice = TRUE;
5799             // kludge follows to temporarily execute move on display, without promoting yet
5800             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5801             boards[currentMove][toY][toX] = p;
5802             DrawPosition(FALSE, boards[currentMove]);
5803             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5804             boards[currentMove][toY][toX] = q;
5805             DisplayMessage("Click in holdings to choose piece", "");
5806             return;
5807         }
5808         PromotionPopUp();
5809     } else {
5810         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5811         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5812         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5813         fromX = fromY = -1;
5814     }
5815     appData.animate = saveAnimate;
5816     if (appData.animate || appData.animateDragging) {
5817         /* Undo animation damage if needed */
5818         DrawPosition(FALSE, NULL);
5819     }
5820 }
5821
5822 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5823 {
5824 //    char * hint = lastHint;
5825     FrontEndProgramStats stats;
5826
5827     stats.which = cps == &first ? 0 : 1;
5828     stats.depth = cpstats->depth;
5829     stats.nodes = cpstats->nodes;
5830     stats.score = cpstats->score;
5831     stats.time = cpstats->time;
5832     stats.pv = cpstats->movelist;
5833     stats.hint = lastHint;
5834     stats.an_move_index = 0;
5835     stats.an_move_count = 0;
5836
5837     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5838         stats.hint = cpstats->move_name;
5839         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5840         stats.an_move_count = cpstats->nr_moves;
5841     }
5842
5843     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5844
5845     SetProgramStats( &stats );
5846 }
5847
5848 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5849 {   // [HGM] book: this routine intercepts moves to simulate book replies
5850     char *bookHit = NULL;
5851
5852     //first determine if the incoming move brings opponent into his book
5853     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5854         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5855     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5856     if(bookHit != NULL && !cps->bookSuspend) {
5857         // make sure opponent is not going to reply after receiving move to book position
5858         SendToProgram("force\n", cps);
5859         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5860     }
5861     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5862     // now arrange restart after book miss
5863     if(bookHit) {
5864         // after a book hit we never send 'go', and the code after the call to this routine
5865         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5866         char buf[MSG_SIZ];
5867         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5868         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5869         SendToProgram(buf, cps);
5870         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5871     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5872         SendToProgram("go\n", cps);
5873         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5874     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5875         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5876             SendToProgram("go\n", cps); 
5877         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5878     }
5879     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5880 }
5881
5882 char *savedMessage;
5883 ChessProgramState *savedState;
5884 void DeferredBookMove(void)
5885 {
5886         if(savedState->lastPing != savedState->lastPong)
5887                     ScheduleDelayedEvent(DeferredBookMove, 10);
5888         else
5889         HandleMachineMove(savedMessage, savedState);
5890 }
5891
5892 void
5893 HandleMachineMove(message, cps)
5894      char *message;
5895      ChessProgramState *cps;
5896 {
5897     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5898     char realname[MSG_SIZ];
5899     int fromX, fromY, toX, toY;
5900     ChessMove moveType;
5901     char promoChar;
5902     char *p;
5903     int machineWhite;
5904     char *bookHit;
5905
5906     cps->userError = 0;
5907
5908 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5909     /*
5910      * Kludge to ignore BEL characters
5911      */
5912     while (*message == '\007') message++;
5913
5914     /*
5915      * [HGM] engine debug message: ignore lines starting with '#' character
5916      */
5917     if(cps->debug && *message == '#') return;
5918
5919     /*
5920      * Look for book output
5921      */
5922     if (cps == &first && bookRequested) {
5923         if (message[0] == '\t' || message[0] == ' ') {
5924             /* Part of the book output is here; append it */
5925             strcat(bookOutput, message);
5926             strcat(bookOutput, "  \n");
5927             return;
5928         } else if (bookOutput[0] != NULLCHAR) {
5929             /* All of book output has arrived; display it */
5930             char *p = bookOutput;
5931             while (*p != NULLCHAR) {
5932                 if (*p == '\t') *p = ' ';
5933                 p++;
5934             }
5935             DisplayInformation(bookOutput);
5936             bookRequested = FALSE;
5937             /* Fall through to parse the current output */
5938         }
5939     }
5940
5941     /*
5942      * Look for machine move.
5943      */
5944     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5945         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5946     {
5947         /* This method is only useful on engines that support ping */
5948         if (cps->lastPing != cps->lastPong) {
5949           if (gameMode == BeginningOfGame) {
5950             /* Extra move from before last new; ignore */
5951             if (appData.debugMode) {
5952                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5953             }
5954           } else {
5955             if (appData.debugMode) {
5956                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5957                         cps->which, gameMode);
5958             }
5959
5960             SendToProgram("undo\n", cps);
5961           }
5962           return;
5963         }
5964
5965         switch (gameMode) {
5966           case BeginningOfGame:
5967             /* Extra move from before last reset; ignore */
5968             if (appData.debugMode) {
5969                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5970             }
5971             return;
5972
5973           case EndOfGame:
5974           case IcsIdle:
5975           default:
5976             /* Extra move after we tried to stop.  The mode test is
5977                not a reliable way of detecting this problem, but it's
5978                the best we can do on engines that don't support ping.
5979             */
5980             if (appData.debugMode) {
5981                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5982                         cps->which, gameMode);
5983             }
5984             SendToProgram("undo\n", cps);
5985             return;
5986
5987           case MachinePlaysWhite:
5988           case IcsPlayingWhite:
5989             machineWhite = TRUE;
5990             break;
5991
5992           case MachinePlaysBlack:
5993           case IcsPlayingBlack:
5994             machineWhite = FALSE;
5995             break;
5996
5997           case TwoMachinesPlay:
5998             machineWhite = (cps->twoMachinesColor[0] == 'w');
5999             break;
6000         }
6001         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6002             if (appData.debugMode) {
6003                 fprintf(debugFP,
6004                         "Ignoring move out of turn by %s, gameMode %d"
6005                         ", forwardMost %d\n",
6006                         cps->which, gameMode, forwardMostMove);
6007             }
6008             return;
6009         }
6010
6011     if (appData.debugMode) { int f = forwardMostMove;
6012         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6013                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6014                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6015     }
6016         if(cps->alphaRank) AlphaRank(machineMove, 4);
6017         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6018                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6019             /* Machine move could not be parsed; ignore it. */
6020             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6021                     machineMove, cps->which);
6022             DisplayError(buf1, 0);
6023             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6024                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6025             if (gameMode == TwoMachinesPlay) {
6026               GameEnds(machineWhite ? BlackWins : WhiteWins,
6027                        buf1, GE_XBOARD);
6028             }
6029             return;
6030         }
6031
6032         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6033         /* So we have to redo legality test with true e.p. status here,  */
6034         /* to make sure an illegal e.p. capture does not slip through,   */
6035         /* to cause a forfeit on a justified illegal-move complaint      */
6036         /* of the opponent.                                              */
6037         if( gameMode==TwoMachinesPlay && appData.testLegality
6038             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6039                                                               ) {
6040            ChessMove moveType;
6041            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6042                              fromY, fromX, toY, toX, promoChar);
6043             if (appData.debugMode) {
6044                 int i;
6045                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6046                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6047                 fprintf(debugFP, "castling rights\n");
6048             }
6049             if(moveType == IllegalMove) {
6050                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6051                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6052                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6053                            buf1, GE_XBOARD);
6054                 return;
6055            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6056            /* [HGM] Kludge to handle engines that send FRC-style castling
6057               when they shouldn't (like TSCP-Gothic) */
6058            switch(moveType) {
6059              case WhiteASideCastleFR:
6060              case BlackASideCastleFR:
6061                toX+=2;
6062                currentMoveString[2]++;
6063                break;
6064              case WhiteHSideCastleFR:
6065              case BlackHSideCastleFR:
6066                toX--;
6067                currentMoveString[2]--;
6068                break;
6069              default: ; // nothing to do, but suppresses warning of pedantic compilers
6070            }
6071         }
6072         hintRequested = FALSE;
6073         lastHint[0] = NULLCHAR;
6074         bookRequested = FALSE;
6075         /* Program may be pondering now */
6076         cps->maybeThinking = TRUE;
6077         if (cps->sendTime == 2) cps->sendTime = 1;
6078         if (cps->offeredDraw) cps->offeredDraw--;
6079
6080 #if ZIPPY
6081         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6082             first.initDone) {
6083           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6084           ics_user_moved = 1;
6085           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6086                 char buf[3*MSG_SIZ];
6087
6088                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6089                         programStats.score / 100.,
6090                         programStats.depth,
6091                         programStats.time / 100.,
6092                         (unsigned int)programStats.nodes,
6093                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6094                         programStats.movelist);
6095                 SendToICS(buf);
6096 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6097           }
6098         }
6099 #endif
6100         /* currentMoveString is set as a side-effect of ParseOneMove */
6101         strcpy(machineMove, currentMoveString);
6102         strcat(machineMove, "\n");
6103         strcpy(moveList[forwardMostMove], machineMove);
6104
6105         /* [AS] Save move info and clear stats for next move */
6106         pvInfoList[ forwardMostMove ].score = programStats.score;
6107         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6108         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6109         ClearProgramStats();
6110         thinkOutput[0] = NULLCHAR;
6111         hiddenThinkOutputState = 0;
6112
6113         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6114
6115         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6116         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6117             int count = 0;
6118
6119             while( count < adjudicateLossPlies ) {
6120                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6121
6122                 if( count & 1 ) {
6123                     score = -score; /* Flip score for winning side */
6124                 }
6125
6126                 if( score > adjudicateLossThreshold ) {
6127                     break;
6128                 }
6129
6130                 count++;
6131             }
6132
6133             if( count >= adjudicateLossPlies ) {
6134                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6135
6136                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6137                     "Xboard adjudication", 
6138                     GE_XBOARD );
6139
6140                 return;
6141             }
6142         }
6143
6144         if( gameMode == TwoMachinesPlay ) {
6145           // [HGM] some adjudications useful with buggy engines
6146             int k, count = 0; static int bare = 1;
6147           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6148
6149
6150             if( appData.testLegality )
6151             {   /* [HGM] Some more adjudications for obstinate engines */
6152                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6153                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6154                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6155                 static int moveCount = 6;
6156                 ChessMove result;
6157                 char *reason = NULL;
6158
6159                 /* Count what is on board. */
6160                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6161                 {   ChessSquare p = boards[forwardMostMove][i][j];
6162                     int m=i;
6163
6164                     switch((int) p)
6165                     {   /* count B,N,R and other of each side */
6166                         case WhiteKing:
6167                         case BlackKing:
6168                              NrK++; break; // [HGM] atomic: count Kings
6169                         case WhiteKnight:
6170                              NrWN++; break;
6171                         case WhiteBishop:
6172                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6173                              bishopsColor |= 1 << ((i^j)&1);
6174                              NrWB++; break;
6175                         case BlackKnight:
6176                              NrBN++; break;
6177                         case BlackBishop:
6178                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6179                              bishopsColor |= 1 << ((i^j)&1);
6180                              NrBB++; break;
6181                         case WhiteRook:
6182                              NrWR++; break;
6183                         case BlackRook:
6184                              NrBR++; break;
6185                         case WhiteQueen:
6186                              NrWQ++; break;
6187                         case BlackQueen:
6188                              NrBQ++; break;
6189                         case EmptySquare: 
6190                              break;
6191                         case BlackPawn:
6192                              m = 7-i;
6193                         case WhitePawn:
6194                              PawnAdvance += m; NrPawns++;
6195                     }
6196                     NrPieces += (p != EmptySquare);
6197                     NrW += ((int)p < (int)BlackPawn);
6198                     if(gameInfo.variant == VariantXiangqi && 
6199                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6200                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6201                         NrW -= ((int)p < (int)BlackPawn);
6202                     }
6203                 }
6204
6205                 /* Some material-based adjudications that have to be made before stalemate test */
6206                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6207                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6208                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6209                      if(appData.checkMates) {
6210                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6211                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6212                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6213                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6214                          return;
6215                      }
6216                 }
6217
6218                 /* Bare King in Shatranj (loses) or Losers (wins) */
6219                 if( NrW == 1 || NrPieces - NrW == 1) {
6220                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6221                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6222                      if(appData.checkMates) {
6223                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6224                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6225                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6226                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6227                          return;
6228                      }
6229                   } else
6230                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6231                   {    /* bare King */
6232                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6233                         if(appData.checkMates) {
6234                             /* but only adjudicate if adjudication enabled */
6235                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6236                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6237                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6238                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6239                             return;
6240                         }
6241                   }
6242                 } else bare = 1;
6243
6244
6245             // don't wait for engine to announce game end if we can judge ourselves
6246             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6247               case MT_CHECK:
6248                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6249                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6250                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6251                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6252                             checkCnt++;
6253                         if(checkCnt >= 2) {
6254                             reason = "Xboard adjudication: 3rd check";
6255                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6256                             break;
6257                         }
6258                     }
6259                 }
6260               case MT_NONE:
6261               default:
6262                 break;
6263               case MT_STALEMATE:
6264               case MT_STAINMATE:
6265                 reason = "Xboard adjudication: Stalemate";
6266                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6267                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6268                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6269                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6270                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6271                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6272                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6273                                                                         EP_CHECKMATE : EP_WINS);
6274                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6275                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6276                 }
6277                 break;
6278               case MT_CHECKMATE:
6279                 reason = "Xboard adjudication: Checkmate";
6280                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6281                 break;
6282             }
6283
6284                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6285                     case EP_STALEMATE:
6286                         result = GameIsDrawn; break;
6287                     case EP_CHECKMATE:
6288                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6289                     case EP_WINS:
6290                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6291                     default:
6292                         result = (ChessMove) 0;
6293                 }
6294                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6295                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6296                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6297                     GameEnds( result, reason, GE_XBOARD );
6298                     return;
6299                 }
6300
6301                 /* Next absolutely insufficient mating material. */
6302                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6303                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6304                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6305                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6306                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6307
6308                      /* always flag draws, for judging claims */
6309                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6310
6311                      if(appData.materialDraws) {
6312                          /* but only adjudicate them if adjudication enabled */
6313                          SendToProgram("force\n", cps->other); // suppress reply
6314                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6315                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6316                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6317                          return;
6318                      }
6319                 }
6320
6321                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6322                 if(NrPieces == 4 && 
6323                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6324                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6325                    || NrWN==2 || NrBN==2     /* KNNK */
6326                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6327                   ) ) {
6328                      if(--moveCount < 0 && appData.trivialDraws)
6329                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6330                           SendToProgram("force\n", cps->other); // suppress reply
6331                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6332                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6333                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6334                           return;
6335                      }
6336                 } else moveCount = 6;
6337             }
6338           }
6339           
6340           if (appData.debugMode) { int i;
6341             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6342                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6343                     appData.drawRepeats);
6344             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6345               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6346             
6347           }
6348
6349                 /* Check for rep-draws */
6350                 count = 0;
6351                 for(k = forwardMostMove-2;
6352                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6353                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6354                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6355                     k-=2)
6356                 {   int rights=0;
6357                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6358                         /* compare castling rights */
6359                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6360                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6361                                 rights++; /* King lost rights, while rook still had them */
6362                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6363                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6364                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6365                                    rights++; /* but at least one rook lost them */
6366                         }
6367                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6368                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6369                                 rights++; 
6370                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6371                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6372                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6373                                    rights++;
6374                         }
6375                         if( rights == 0 && ++count > appData.drawRepeats-2
6376                             && appData.drawRepeats > 1) {
6377                              /* adjudicate after user-specified nr of repeats */
6378                              SendToProgram("force\n", cps->other); // suppress reply
6379                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6380                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6381                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6382                                 // [HGM] xiangqi: check for forbidden perpetuals
6383                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6384                                 for(m=forwardMostMove; m>k; m-=2) {
6385                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6386                                         ourPerpetual = 0; // the current mover did not always check
6387                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6388                                         hisPerpetual = 0; // the opponent did not always check
6389                                 }
6390                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6391                                                                         ourPerpetual, hisPerpetual);
6392                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6393                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6394                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6395                                     return;
6396                                 }
6397                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6398                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6399                                 // Now check for perpetual chases
6400                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6401                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6402                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6403                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6404                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6405                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6406                                         return;
6407                                     }
6408                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6409                                         break; // Abort repetition-checking loop.
6410                                 }
6411                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6412                              }
6413                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6414                              return;
6415                         }
6416                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6417                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6418                     }
6419                 }
6420
6421                 /* Now we test for 50-move draws. Determine ply count */
6422                 count = forwardMostMove;
6423                 /* look for last irreversble move */
6424                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6425                     count--;
6426                 /* if we hit starting position, add initial plies */
6427                 if( count == backwardMostMove )
6428                     count -= initialRulePlies;
6429                 count = forwardMostMove - count; 
6430                 if( count >= 100)
6431                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6432                          /* this is used to judge if draw claims are legal */
6433                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6434                          SendToProgram("force\n", cps->other); // suppress reply
6435                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6436                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6437                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6438                          return;
6439                 }
6440
6441                 /* if draw offer is pending, treat it as a draw claim
6442                  * when draw condition present, to allow engines a way to
6443                  * claim draws before making their move to avoid a race
6444                  * condition occurring after their move
6445                  */
6446                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6447                          char *p = NULL;
6448                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6449                              p = "Draw claim: 50-move rule";
6450                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6451                              p = "Draw claim: 3-fold repetition";
6452                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6453                              p = "Draw claim: insufficient mating material";
6454                          if( p != NULL ) {
6455                              SendToProgram("force\n", cps->other); // suppress reply
6456                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6457                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6458                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6459                              return;
6460                          }
6461                 }
6462
6463
6464                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6465                     SendToProgram("force\n", cps->other); // suppress reply
6466                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6467                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6468
6469                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6470
6471                     return;
6472                 }
6473         }
6474
6475         bookHit = NULL;
6476         if (gameMode == TwoMachinesPlay) {
6477             /* [HGM] relaying draw offers moved to after reception of move */
6478             /* and interpreting offer as claim if it brings draw condition */
6479             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6480                 SendToProgram("draw\n", cps->other);
6481             }
6482             if (cps->other->sendTime) {
6483                 SendTimeRemaining(cps->other,
6484                                   cps->other->twoMachinesColor[0] == 'w');
6485             }
6486             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6487             if (firstMove && !bookHit) {
6488                 firstMove = FALSE;
6489                 if (cps->other->useColors) {
6490                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6491                 }
6492                 SendToProgram("go\n", cps->other);
6493             }
6494             cps->other->maybeThinking = TRUE;
6495         }
6496
6497         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6498         
6499         if (!pausing && appData.ringBellAfterMoves) {
6500             RingBell();
6501         }
6502
6503         /* 
6504          * Reenable menu items that were disabled while
6505          * machine was thinking
6506          */
6507         if (gameMode != TwoMachinesPlay)
6508             SetUserThinkingEnables();
6509
6510         // [HGM] book: after book hit opponent has received move and is now in force mode
6511         // force the book reply into it, and then fake that it outputted this move by jumping
6512         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6513         if(bookHit) {
6514                 static char bookMove[MSG_SIZ]; // a bit generous?
6515
6516                 strcpy(bookMove, "move ");
6517                 strcat(bookMove, bookHit);
6518                 message = bookMove;
6519                 cps = cps->other;
6520                 programStats.nodes = programStats.depth = programStats.time = 
6521                 programStats.score = programStats.got_only_move = 0;
6522                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6523
6524                 if(cps->lastPing != cps->lastPong) {
6525                     savedMessage = message; // args for deferred call
6526                     savedState = cps;
6527                     ScheduleDelayedEvent(DeferredBookMove, 10);
6528                     return;
6529                 }
6530                 goto FakeBookMove;
6531         }
6532
6533         return;
6534     }
6535
6536     /* Set special modes for chess engines.  Later something general
6537      *  could be added here; for now there is just one kludge feature,
6538      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6539      *  when "xboard" is given as an interactive command.
6540      */
6541     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6542         cps->useSigint = FALSE;
6543         cps->useSigterm = FALSE;
6544     }
6545     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6546       ParseFeatures(message+8, cps);
6547       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6548     }
6549
6550     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6551      * want this, I was asked to put it in, and obliged.
6552      */
6553     if (!strncmp(message, "setboard ", 9)) {
6554         Board initial_position;
6555
6556         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6557
6558         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6559             DisplayError(_("Bad FEN received from engine"), 0);
6560             return ;
6561         } else {
6562            Reset(TRUE, FALSE);
6563            CopyBoard(boards[0], initial_position);
6564            initialRulePlies = FENrulePlies;
6565            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6566            else gameMode = MachinePlaysBlack;                 
6567            DrawPosition(FALSE, boards[currentMove]);
6568         }
6569         return;
6570     }
6571
6572     /*
6573      * Look for communication commands
6574      */
6575     if (!strncmp(message, "telluser ", 9)) {
6576         DisplayNote(message + 9);
6577         return;
6578     }
6579     if (!strncmp(message, "tellusererror ", 14)) {
6580         cps->userError = 1;
6581         DisplayError(message + 14, 0);
6582         return;
6583     }
6584     if (!strncmp(message, "tellopponent ", 13)) {
6585       if (appData.icsActive) {
6586         if (loggedOn) {
6587           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6588           SendToICS(buf1);
6589         }
6590       } else {
6591         DisplayNote(message + 13);
6592       }
6593       return;
6594     }
6595     if (!strncmp(message, "tellothers ", 11)) {
6596       if (appData.icsActive) {
6597         if (loggedOn) {
6598           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6599           SendToICS(buf1);
6600         }
6601       }
6602       return;
6603     }
6604     if (!strncmp(message, "tellall ", 8)) {
6605       if (appData.icsActive) {
6606         if (loggedOn) {
6607           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6608           SendToICS(buf1);
6609         }
6610       } else {
6611         DisplayNote(message + 8);
6612       }
6613       return;
6614     }
6615     if (strncmp(message, "warning", 7) == 0) {
6616         /* Undocumented feature, use tellusererror in new code */
6617         DisplayError(message, 0);
6618         return;
6619     }
6620     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6621         strcpy(realname, cps->tidy);
6622         strcat(realname, " query");
6623         AskQuestion(realname, buf2, buf1, cps->pr);
6624         return;
6625     }
6626     /* Commands from the engine directly to ICS.  We don't allow these to be 
6627      *  sent until we are logged on. Crafty kibitzes have been known to 
6628      *  interfere with the login process.
6629      */
6630     if (loggedOn) {
6631         if (!strncmp(message, "tellics ", 8)) {
6632             SendToICS(message + 8);
6633             SendToICS("\n");
6634             return;
6635         }
6636         if (!strncmp(message, "tellicsnoalias ", 15)) {
6637             SendToICS(ics_prefix);
6638             SendToICS(message + 15);
6639             SendToICS("\n");
6640             return;
6641         }
6642         /* The following are for backward compatibility only */
6643         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6644             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6645             SendToICS(ics_prefix);
6646             SendToICS(message);
6647             SendToICS("\n");
6648             return;
6649         }
6650     }
6651     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6652         return;
6653     }
6654     /*
6655      * If the move is illegal, cancel it and redraw the board.
6656      * Also deal with other error cases.  Matching is rather loose
6657      * here to accommodate engines written before the spec.
6658      */
6659     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6660         strncmp(message, "Error", 5) == 0) {
6661         if (StrStr(message, "name") || 
6662             StrStr(message, "rating") || StrStr(message, "?") ||
6663             StrStr(message, "result") || StrStr(message, "board") ||
6664             StrStr(message, "bk") || StrStr(message, "computer") ||
6665             StrStr(message, "variant") || StrStr(message, "hint") ||
6666             StrStr(message, "random") || StrStr(message, "depth") ||
6667             StrStr(message, "accepted")) {
6668             return;
6669         }
6670         if (StrStr(message, "protover")) {
6671           /* Program is responding to input, so it's apparently done
6672              initializing, and this error message indicates it is
6673              protocol version 1.  So we don't need to wait any longer
6674              for it to initialize and send feature commands. */
6675           FeatureDone(cps, 1);
6676           cps->protocolVersion = 1;
6677           return;
6678         }
6679         cps->maybeThinking = FALSE;
6680
6681         if (StrStr(message, "draw")) {
6682             /* Program doesn't have "draw" command */
6683             cps->sendDrawOffers = 0;
6684             return;
6685         }
6686         if (cps->sendTime != 1 &&
6687             (StrStr(message, "time") || StrStr(message, "otim"))) {
6688           /* Program apparently doesn't have "time" or "otim" command */
6689           cps->sendTime = 0;
6690           return;
6691         }
6692         if (StrStr(message, "analyze")) {
6693             cps->analysisSupport = FALSE;
6694             cps->analyzing = FALSE;
6695             Reset(FALSE, TRUE);
6696             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6697             DisplayError(buf2, 0);
6698             return;
6699         }
6700         if (StrStr(message, "(no matching move)st")) {
6701           /* Special kludge for GNU Chess 4 only */
6702           cps->stKludge = TRUE;
6703           SendTimeControl(cps, movesPerSession, timeControl,
6704                           timeIncrement, appData.searchDepth,
6705                           searchTime);
6706           return;
6707         }
6708         if (StrStr(message, "(no matching move)sd")) {
6709           /* Special kludge for GNU Chess 4 only */
6710           cps->sdKludge = TRUE;
6711           SendTimeControl(cps, movesPerSession, timeControl,
6712                           timeIncrement, appData.searchDepth,
6713                           searchTime);
6714           return;
6715         }
6716         if (!StrStr(message, "llegal")) {
6717             return;
6718         }
6719         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6720             gameMode == IcsIdle) return;
6721         if (forwardMostMove <= backwardMostMove) return;
6722         if (pausing) PauseEvent();
6723       if(appData.forceIllegal) {
6724             // [HGM] illegal: machine refused move; force position after move into it
6725           SendToProgram("force\n", cps);
6726           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6727                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6728                 // when black is to move, while there might be nothing on a2 or black
6729                 // might already have the move. So send the board as if white has the move.
6730                 // But first we must change the stm of the engine, as it refused the last move
6731                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6732                 if(WhiteOnMove(forwardMostMove)) {
6733                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6734                     SendBoard(cps, forwardMostMove); // kludgeless board
6735                 } else {
6736                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6737                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6738                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6739                 }
6740           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6741             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6742                  gameMode == TwoMachinesPlay)
6743               SendToProgram("go\n", cps);
6744             return;
6745       } else
6746         if (gameMode == PlayFromGameFile) {
6747             /* Stop reading this game file */
6748             gameMode = EditGame;
6749             ModeHighlight();
6750         }
6751         currentMove = --forwardMostMove;
6752         DisplayMove(currentMove-1); /* before DisplayMoveError */
6753         SwitchClocks();
6754         DisplayBothClocks();
6755         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6756                 parseList[currentMove], cps->which);
6757         DisplayMoveError(buf1);
6758         DrawPosition(FALSE, boards[currentMove]);
6759
6760         /* [HGM] illegal-move claim should forfeit game when Xboard */
6761         /* only passes fully legal moves                            */
6762         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6763             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6764                                 "False illegal-move claim", GE_XBOARD );
6765         }
6766         return;
6767     }
6768     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6769         /* Program has a broken "time" command that
6770            outputs a string not ending in newline.
6771            Don't use it. */
6772         cps->sendTime = 0;
6773     }
6774     
6775     /*
6776      * If chess program startup fails, exit with an error message.
6777      * Attempts to recover here are futile.
6778      */
6779     if ((StrStr(message, "unknown host") != NULL)
6780         || (StrStr(message, "No remote directory") != NULL)
6781         || (StrStr(message, "not found") != NULL)
6782         || (StrStr(message, "No such file") != NULL)
6783         || (StrStr(message, "can't alloc") != NULL)
6784         || (StrStr(message, "Permission denied") != NULL)) {
6785
6786         cps->maybeThinking = FALSE;
6787         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6788                 cps->which, cps->program, cps->host, message);
6789         RemoveInputSource(cps->isr);
6790         DisplayFatalError(buf1, 0, 1);
6791         return;
6792     }
6793     
6794     /* 
6795      * Look for hint output
6796      */
6797     if (sscanf(message, "Hint: %s", buf1) == 1) {
6798         if (cps == &first && hintRequested) {
6799             hintRequested = FALSE;
6800             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6801                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6802                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6803                                     PosFlags(forwardMostMove),
6804                                     fromY, fromX, toY, toX, promoChar, buf1);
6805                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6806                 DisplayInformation(buf2);
6807             } else {
6808                 /* Hint move could not be parsed!? */
6809               snprintf(buf2, sizeof(buf2),
6810                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6811                         buf1, cps->which);
6812                 DisplayError(buf2, 0);
6813             }
6814         } else {
6815             strcpy(lastHint, buf1);
6816         }
6817         return;
6818     }
6819
6820     /*
6821      * Ignore other messages if game is not in progress
6822      */
6823     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6824         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6825
6826     /*
6827      * look for win, lose, draw, or draw offer
6828      */
6829     if (strncmp(message, "1-0", 3) == 0) {
6830         char *p, *q, *r = "";
6831         p = strchr(message, '{');
6832         if (p) {
6833             q = strchr(p, '}');
6834             if (q) {
6835                 *q = NULLCHAR;
6836                 r = p + 1;
6837             }
6838         }
6839         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6840         return;
6841     } else if (strncmp(message, "0-1", 3) == 0) {
6842         char *p, *q, *r = "";
6843         p = strchr(message, '{');
6844         if (p) {
6845             q = strchr(p, '}');
6846             if (q) {
6847                 *q = NULLCHAR;
6848                 r = p + 1;
6849             }
6850         }
6851         /* Kludge for Arasan 4.1 bug */
6852         if (strcmp(r, "Black resigns") == 0) {
6853             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6854             return;
6855         }
6856         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6857         return;
6858     } else if (strncmp(message, "1/2", 3) == 0) {
6859         char *p, *q, *r = "";
6860         p = strchr(message, '{');
6861         if (p) {
6862             q = strchr(p, '}');
6863             if (q) {
6864                 *q = NULLCHAR;
6865                 r = p + 1;
6866             }
6867         }
6868             
6869         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6870         return;
6871
6872     } else if (strncmp(message, "White resign", 12) == 0) {
6873         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6874         return;
6875     } else if (strncmp(message, "Black resign", 12) == 0) {
6876         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6877         return;
6878     } else if (strncmp(message, "White matches", 13) == 0 ||
6879                strncmp(message, "Black matches", 13) == 0   ) {
6880         /* [HGM] ignore GNUShogi noises */
6881         return;
6882     } else if (strncmp(message, "White", 5) == 0 &&
6883                message[5] != '(' &&
6884                StrStr(message, "Black") == NULL) {
6885         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6886         return;
6887     } else if (strncmp(message, "Black", 5) == 0 &&
6888                message[5] != '(') {
6889         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6890         return;
6891     } else if (strcmp(message, "resign") == 0 ||
6892                strcmp(message, "computer resigns") == 0) {
6893         switch (gameMode) {
6894           case MachinePlaysBlack:
6895           case IcsPlayingBlack:
6896             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6897             break;
6898           case MachinePlaysWhite:
6899           case IcsPlayingWhite:
6900             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6901             break;
6902           case TwoMachinesPlay:
6903             if (cps->twoMachinesColor[0] == 'w')
6904               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6905             else
6906               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6907             break;
6908           default:
6909             /* can't happen */
6910             break;
6911         }
6912         return;
6913     } else if (strncmp(message, "opponent mates", 14) == 0) {
6914         switch (gameMode) {
6915           case MachinePlaysBlack:
6916           case IcsPlayingBlack:
6917             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6918             break;
6919           case MachinePlaysWhite:
6920           case IcsPlayingWhite:
6921             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6922             break;
6923           case TwoMachinesPlay:
6924             if (cps->twoMachinesColor[0] == 'w')
6925               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6926             else
6927               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6928             break;
6929           default:
6930             /* can't happen */
6931             break;
6932         }
6933         return;
6934     } else if (strncmp(message, "computer mates", 14) == 0) {
6935         switch (gameMode) {
6936           case MachinePlaysBlack:
6937           case IcsPlayingBlack:
6938             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6939             break;
6940           case MachinePlaysWhite:
6941           case IcsPlayingWhite:
6942             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6943             break;
6944           case TwoMachinesPlay:
6945             if (cps->twoMachinesColor[0] == 'w')
6946               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6947             else
6948               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6949             break;
6950           default:
6951             /* can't happen */
6952             break;
6953         }
6954         return;
6955     } else if (strncmp(message, "checkmate", 9) == 0) {
6956         if (WhiteOnMove(forwardMostMove)) {
6957             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6958         } else {
6959             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6960         }
6961         return;
6962     } else if (strstr(message, "Draw") != NULL ||
6963                strstr(message, "game is a draw") != NULL) {
6964         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6965         return;
6966     } else if (strstr(message, "offer") != NULL &&
6967                strstr(message, "draw") != NULL) {
6968 #if ZIPPY
6969         if (appData.zippyPlay && first.initDone) {
6970             /* Relay offer to ICS */
6971             SendToICS(ics_prefix);
6972             SendToICS("draw\n");
6973         }
6974 #endif
6975         cps->offeredDraw = 2; /* valid until this engine moves twice */
6976         if (gameMode == TwoMachinesPlay) {
6977             if (cps->other->offeredDraw) {
6978                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6979             /* [HGM] in two-machine mode we delay relaying draw offer      */
6980             /* until after we also have move, to see if it is really claim */
6981             }
6982         } else if (gameMode == MachinePlaysWhite ||
6983                    gameMode == MachinePlaysBlack) {
6984           if (userOfferedDraw) {
6985             DisplayInformation(_("Machine accepts your draw offer"));
6986             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6987           } else {
6988             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6989           }
6990         }
6991     }
6992
6993     
6994     /*
6995      * Look for thinking output
6996      */
6997     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6998           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6999                                 ) {
7000         int plylev, mvleft, mvtot, curscore, time;
7001         char mvname[MOVE_LEN];
7002         u64 nodes; // [DM]
7003         char plyext;
7004         int ignore = FALSE;
7005         int prefixHint = FALSE;
7006         mvname[0] = NULLCHAR;
7007
7008         switch (gameMode) {
7009           case MachinePlaysBlack:
7010           case IcsPlayingBlack:
7011             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7012             break;
7013           case MachinePlaysWhite:
7014           case IcsPlayingWhite:
7015             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7016             break;
7017           case AnalyzeMode:
7018           case AnalyzeFile:
7019             break;
7020           case IcsObserving: /* [DM] icsEngineAnalyze */
7021             if (!appData.icsEngineAnalyze) ignore = TRUE;
7022             break;
7023           case TwoMachinesPlay:
7024             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7025                 ignore = TRUE;
7026             }
7027             break;
7028           default:
7029             ignore = TRUE;
7030             break;
7031         }
7032
7033         if (!ignore) {
7034             buf1[0] = NULLCHAR;
7035             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7036                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7037
7038                 if (plyext != ' ' && plyext != '\t') {
7039                     time *= 100;
7040                 }
7041
7042                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7043                 if( cps->scoreIsAbsolute && 
7044                     ( gameMode == MachinePlaysBlack ||
7045                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7046                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7047                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7048                      !WhiteOnMove(currentMove)
7049                     ) )
7050                 {
7051                     curscore = -curscore;
7052                 }
7053
7054
7055                 programStats.depth = plylev;
7056                 programStats.nodes = nodes;
7057                 programStats.time = time;
7058                 programStats.score = curscore;
7059                 programStats.got_only_move = 0;
7060
7061                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7062                         int ticklen;
7063
7064                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7065                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7066                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7067                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7068                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7069                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7070                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7071                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7072                 }
7073
7074                 /* Buffer overflow protection */
7075                 if (buf1[0] != NULLCHAR) {
7076                     if (strlen(buf1) >= sizeof(programStats.movelist)
7077                         && appData.debugMode) {
7078                         fprintf(debugFP,
7079                                 "PV is too long; using the first %u bytes.\n",
7080                                 (unsigned) sizeof(programStats.movelist) - 1);
7081                     }
7082
7083                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7084                 } else {
7085                     sprintf(programStats.movelist, " no PV\n");
7086                 }
7087
7088                 if (programStats.seen_stat) {
7089                     programStats.ok_to_send = 1;
7090                 }
7091
7092                 if (strchr(programStats.movelist, '(') != NULL) {
7093                     programStats.line_is_book = 1;
7094                     programStats.nr_moves = 0;
7095                     programStats.moves_left = 0;
7096                 } else {
7097                     programStats.line_is_book = 0;
7098                 }
7099
7100                 SendProgramStatsToFrontend( cps, &programStats );
7101
7102                 /* 
7103                     [AS] Protect the thinkOutput buffer from overflow... this
7104                     is only useful if buf1 hasn't overflowed first!
7105                 */
7106                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7107                         plylev, 
7108                         (gameMode == TwoMachinesPlay ?
7109                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7110                         ((double) curscore) / 100.0,
7111                         prefixHint ? lastHint : "",
7112                         prefixHint ? " " : "" );
7113
7114                 if( buf1[0] != NULLCHAR ) {
7115                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7116
7117                     if( strlen(buf1) > max_len ) {
7118                         if( appData.debugMode) {
7119                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7120                         }
7121                         buf1[max_len+1] = '\0';
7122                     }
7123
7124                     strcat( thinkOutput, buf1 );
7125                 }
7126
7127                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7128                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7129                     DisplayMove(currentMove - 1);
7130                 }
7131                 return;
7132
7133             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7134                 /* crafty (9.25+) says "(only move) <move>"
7135                  * if there is only 1 legal move
7136                  */
7137                 sscanf(p, "(only move) %s", buf1);
7138                 sprintf(thinkOutput, "%s (only move)", buf1);
7139                 sprintf(programStats.movelist, "%s (only move)", buf1);
7140                 programStats.depth = 1;
7141                 programStats.nr_moves = 1;
7142                 programStats.moves_left = 1;
7143                 programStats.nodes = 1;
7144                 programStats.time = 1;
7145                 programStats.got_only_move = 1;
7146
7147                 /* Not really, but we also use this member to
7148                    mean "line isn't going to change" (Crafty
7149                    isn't searching, so stats won't change) */
7150                 programStats.line_is_book = 1;
7151
7152                 SendProgramStatsToFrontend( cps, &programStats );
7153                 
7154                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7155                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7156                     DisplayMove(currentMove - 1);
7157                 }
7158                 return;
7159             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7160                               &time, &nodes, &plylev, &mvleft,
7161                               &mvtot, mvname) >= 5) {
7162                 /* The stat01: line is from Crafty (9.29+) in response
7163                    to the "." command */
7164                 programStats.seen_stat = 1;
7165                 cps->maybeThinking = TRUE;
7166
7167                 if (programStats.got_only_move || !appData.periodicUpdates)
7168                   return;
7169
7170                 programStats.depth = plylev;
7171                 programStats.time = time;
7172                 programStats.nodes = nodes;
7173                 programStats.moves_left = mvleft;
7174                 programStats.nr_moves = mvtot;
7175                 strcpy(programStats.move_name, mvname);
7176                 programStats.ok_to_send = 1;
7177                 programStats.movelist[0] = '\0';
7178
7179                 SendProgramStatsToFrontend( cps, &programStats );
7180
7181                 return;
7182
7183             } else if (strncmp(message,"++",2) == 0) {
7184                 /* Crafty 9.29+ outputs this */
7185                 programStats.got_fail = 2;
7186                 return;
7187
7188             } else if (strncmp(message,"--",2) == 0) {
7189                 /* Crafty 9.29+ outputs this */
7190                 programStats.got_fail = 1;
7191                 return;
7192
7193             } else if (thinkOutput[0] != NULLCHAR &&
7194                        strncmp(message, "    ", 4) == 0) {
7195                 unsigned message_len;
7196
7197                 p = message;
7198                 while (*p && *p == ' ') p++;
7199
7200                 message_len = strlen( p );
7201
7202                 /* [AS] Avoid buffer overflow */
7203                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7204                     strcat(thinkOutput, " ");
7205                     strcat(thinkOutput, p);
7206                 }
7207
7208                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7209                     strcat(programStats.movelist, " ");
7210                     strcat(programStats.movelist, p);
7211                 }
7212
7213                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7214                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7215                     DisplayMove(currentMove - 1);
7216                 }
7217                 return;
7218             }
7219         }
7220         else {
7221             buf1[0] = NULLCHAR;
7222
7223             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7224                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7225             {
7226                 ChessProgramStats cpstats;
7227
7228                 if (plyext != ' ' && plyext != '\t') {
7229                     time *= 100;
7230                 }
7231
7232                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7233                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7234                     curscore = -curscore;
7235                 }
7236
7237                 cpstats.depth = plylev;
7238                 cpstats.nodes = nodes;
7239                 cpstats.time = time;
7240                 cpstats.score = curscore;
7241                 cpstats.got_only_move = 0;
7242                 cpstats.movelist[0] = '\0';
7243
7244                 if (buf1[0] != NULLCHAR) {
7245                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7246                 }
7247
7248                 cpstats.ok_to_send = 0;
7249                 cpstats.line_is_book = 0;
7250                 cpstats.nr_moves = 0;
7251                 cpstats.moves_left = 0;
7252
7253                 SendProgramStatsToFrontend( cps, &cpstats );
7254             }
7255         }
7256     }
7257 }
7258
7259
7260 /* Parse a game score from the character string "game", and
7261    record it as the history of the current game.  The game
7262    score is NOT assumed to start from the standard position. 
7263    The display is not updated in any way.
7264    */
7265 void
7266 ParseGameHistory(game)
7267      char *game;
7268 {
7269     ChessMove moveType;
7270     int fromX, fromY, toX, toY, boardIndex;
7271     char promoChar;
7272     char *p, *q;
7273     char buf[MSG_SIZ];
7274
7275     if (appData.debugMode)
7276       fprintf(debugFP, "Parsing game history: %s\n", game);
7277
7278     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7279     gameInfo.site = StrSave(appData.icsHost);
7280     gameInfo.date = PGNDate();
7281     gameInfo.round = StrSave("-");
7282
7283     /* Parse out names of players */
7284     while (*game == ' ') game++;
7285     p = buf;
7286     while (*game != ' ') *p++ = *game++;
7287     *p = NULLCHAR;
7288     gameInfo.white = StrSave(buf);
7289     while (*game == ' ') game++;
7290     p = buf;
7291     while (*game != ' ' && *game != '\n') *p++ = *game++;
7292     *p = NULLCHAR;
7293     gameInfo.black = StrSave(buf);
7294
7295     /* Parse moves */
7296     boardIndex = blackPlaysFirst ? 1 : 0;
7297     yynewstr(game);
7298     for (;;) {
7299         yyboardindex = boardIndex;
7300         moveType = (ChessMove) yylex();
7301         switch (moveType) {
7302           case IllegalMove:             /* maybe suicide chess, etc. */
7303   if (appData.debugMode) {
7304     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7305     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7306     setbuf(debugFP, NULL);
7307   }
7308           case WhitePromotionChancellor:
7309           case BlackPromotionChancellor:
7310           case WhitePromotionArchbishop:
7311           case BlackPromotionArchbishop:
7312           case WhitePromotionQueen:
7313           case BlackPromotionQueen:
7314           case WhitePromotionRook:
7315           case BlackPromotionRook:
7316           case WhitePromotionBishop:
7317           case BlackPromotionBishop:
7318           case WhitePromotionKnight:
7319           case BlackPromotionKnight:
7320           case WhitePromotionKing:
7321           case BlackPromotionKing:
7322           case NormalMove:
7323           case WhiteCapturesEnPassant:
7324           case BlackCapturesEnPassant:
7325           case WhiteKingSideCastle:
7326           case WhiteQueenSideCastle:
7327           case BlackKingSideCastle:
7328           case BlackQueenSideCastle:
7329           case WhiteKingSideCastleWild:
7330           case WhiteQueenSideCastleWild:
7331           case BlackKingSideCastleWild:
7332           case BlackQueenSideCastleWild:
7333           /* PUSH Fabien */
7334           case WhiteHSideCastleFR:
7335           case WhiteASideCastleFR:
7336           case BlackHSideCastleFR:
7337           case BlackASideCastleFR:
7338           /* POP Fabien */
7339             fromX = currentMoveString[0] - AAA;
7340             fromY = currentMoveString[1] - ONE;
7341             toX = currentMoveString[2] - AAA;
7342             toY = currentMoveString[3] - ONE;
7343             promoChar = currentMoveString[4];
7344             break;
7345           case WhiteDrop:
7346           case BlackDrop:
7347             fromX = moveType == WhiteDrop ?
7348               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7349             (int) CharToPiece(ToLower(currentMoveString[0]));
7350             fromY = DROP_RANK;
7351             toX = currentMoveString[2] - AAA;
7352             toY = currentMoveString[3] - ONE;
7353             promoChar = NULLCHAR;
7354             break;
7355           case AmbiguousMove:
7356             /* bug? */
7357             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7358   if (appData.debugMode) {
7359     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7360     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7361     setbuf(debugFP, NULL);
7362   }
7363             DisplayError(buf, 0);
7364             return;
7365           case ImpossibleMove:
7366             /* bug? */
7367             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7368   if (appData.debugMode) {
7369     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7370     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7371     setbuf(debugFP, NULL);
7372   }
7373             DisplayError(buf, 0);
7374             return;
7375           case (ChessMove) 0:   /* end of file */
7376             if (boardIndex < backwardMostMove) {
7377                 /* Oops, gap.  How did that happen? */
7378                 DisplayError(_("Gap in move list"), 0);
7379                 return;
7380             }
7381             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7382             if (boardIndex > forwardMostMove) {
7383                 forwardMostMove = boardIndex;
7384             }
7385             return;
7386           case ElapsedTime:
7387             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7388                 strcat(parseList[boardIndex-1], " ");
7389                 strcat(parseList[boardIndex-1], yy_text);
7390             }
7391             continue;
7392           case Comment:
7393           case PGNTag:
7394           case NAG:
7395           default:
7396             /* ignore */
7397             continue;
7398           case WhiteWins:
7399           case BlackWins:
7400           case GameIsDrawn:
7401           case GameUnfinished:
7402             if (gameMode == IcsExamining) {
7403                 if (boardIndex < backwardMostMove) {
7404                     /* Oops, gap.  How did that happen? */
7405                     return;
7406                 }
7407                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7408                 return;
7409             }
7410             gameInfo.result = moveType;
7411             p = strchr(yy_text, '{');
7412             if (p == NULL) p = strchr(yy_text, '(');
7413             if (p == NULL) {
7414                 p = yy_text;
7415                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7416             } else {
7417                 q = strchr(p, *p == '{' ? '}' : ')');
7418                 if (q != NULL) *q = NULLCHAR;
7419                 p++;
7420             }
7421             gameInfo.resultDetails = StrSave(p);
7422             continue;
7423         }
7424         if (boardIndex >= forwardMostMove &&
7425             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7426             backwardMostMove = blackPlaysFirst ? 1 : 0;
7427             return;
7428         }
7429         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7430                                  fromY, fromX, toY, toX, promoChar,
7431                                  parseList[boardIndex]);
7432         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7433         /* currentMoveString is set as a side-effect of yylex */
7434         strcpy(moveList[boardIndex], currentMoveString);
7435         strcat(moveList[boardIndex], "\n");
7436         boardIndex++;
7437         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7438         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7439           case MT_NONE:
7440           case MT_STALEMATE:
7441           default:
7442             break;
7443           case MT_CHECK:
7444             if(gameInfo.variant != VariantShogi)
7445                 strcat(parseList[boardIndex - 1], "+");
7446             break;
7447           case MT_CHECKMATE:
7448           case MT_STAINMATE:
7449             strcat(parseList[boardIndex - 1], "#");
7450             break;
7451         }
7452     }
7453 }
7454
7455
7456 /* Apply a move to the given board  */
7457 void
7458 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7459      int fromX, fromY, toX, toY;
7460      int promoChar;
7461      Board board;
7462 {
7463   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7464
7465     /* [HGM] compute & store e.p. status and castling rights for new position */
7466     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7467     { int i;
7468
7469       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7470       oldEP = (signed char)board[EP_STATUS];
7471       board[EP_STATUS] = EP_NONE;
7472
7473       if( board[toY][toX] != EmptySquare ) 
7474            board[EP_STATUS] = EP_CAPTURE;  
7475
7476       if( board[fromY][fromX] == WhitePawn ) {
7477            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7478                board[EP_STATUS] = EP_PAWN_MOVE;
7479            if( toY-fromY==2) {
7480                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7481                         gameInfo.variant != VariantBerolina || toX < fromX)
7482                       board[EP_STATUS] = toX | berolina;
7483                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7484                         gameInfo.variant != VariantBerolina || toX > fromX) 
7485                       board[EP_STATUS] = toX;
7486            }
7487       } else 
7488       if( board[fromY][fromX] == BlackPawn ) {
7489            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7490                board[EP_STATUS] = EP_PAWN_MOVE; 
7491            if( toY-fromY== -2) {
7492                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7493                         gameInfo.variant != VariantBerolina || toX < fromX)
7494                       board[EP_STATUS] = toX | berolina;
7495                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7496                         gameInfo.variant != VariantBerolina || toX > fromX) 
7497                       board[EP_STATUS] = toX;
7498            }
7499        }
7500
7501        for(i=0; i<nrCastlingRights; i++) {
7502            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7503               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7504              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7505        }
7506
7507     }
7508
7509   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7510   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7511        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7512          
7513   if (fromX == toX && fromY == toY) return;
7514
7515   if (fromY == DROP_RANK) {
7516         /* must be first */
7517         piece = board[toY][toX] = (ChessSquare) fromX;
7518   } else {
7519      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7520      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7521      if(gameInfo.variant == VariantKnightmate)
7522          king += (int) WhiteUnicorn - (int) WhiteKing;
7523
7524     /* Code added by Tord: */
7525     /* FRC castling assumed when king captures friendly rook. */
7526     if (board[fromY][fromX] == WhiteKing &&
7527              board[toY][toX] == WhiteRook) {
7528       board[fromY][fromX] = EmptySquare;
7529       board[toY][toX] = EmptySquare;
7530       if(toX > fromX) {
7531         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7532       } else {
7533         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7534       }
7535     } else if (board[fromY][fromX] == BlackKing &&
7536                board[toY][toX] == BlackRook) {
7537       board[fromY][fromX] = EmptySquare;
7538       board[toY][toX] = EmptySquare;
7539       if(toX > fromX) {
7540         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7541       } else {
7542         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7543       }
7544     /* End of code added by Tord */
7545
7546     } else if (board[fromY][fromX] == king
7547         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7548         && toY == fromY && toX > fromX+1) {
7549         board[fromY][fromX] = EmptySquare;
7550         board[toY][toX] = king;
7551         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7552         board[fromY][BOARD_RGHT-1] = EmptySquare;
7553     } else if (board[fromY][fromX] == king
7554         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7555                && toY == fromY && toX < fromX-1) {
7556         board[fromY][fromX] = EmptySquare;
7557         board[toY][toX] = king;
7558         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7559         board[fromY][BOARD_LEFT] = EmptySquare;
7560     } else if (board[fromY][fromX] == WhitePawn
7561                && toY == BOARD_HEIGHT-1
7562                && gameInfo.variant != VariantXiangqi
7563                ) {
7564         /* white pawn promotion */
7565         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7566         if (board[toY][toX] == EmptySquare) {
7567             board[toY][toX] = WhiteQueen;
7568         }
7569         if(gameInfo.variant==VariantBughouse ||
7570            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7571             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7572         board[fromY][fromX] = EmptySquare;
7573     } else if ((fromY == BOARD_HEIGHT-4)
7574                && (toX != fromX)
7575                && gameInfo.variant != VariantXiangqi
7576                && gameInfo.variant != VariantBerolina
7577                && (board[fromY][fromX] == WhitePawn)
7578                && (board[toY][toX] == EmptySquare)) {
7579         board[fromY][fromX] = EmptySquare;
7580         board[toY][toX] = WhitePawn;
7581         captured = board[toY - 1][toX];
7582         board[toY - 1][toX] = EmptySquare;
7583     } else if ((fromY == BOARD_HEIGHT-4)
7584                && (toX == fromX)
7585                && gameInfo.variant == VariantBerolina
7586                && (board[fromY][fromX] == WhitePawn)
7587                && (board[toY][toX] == EmptySquare)) {
7588         board[fromY][fromX] = EmptySquare;
7589         board[toY][toX] = WhitePawn;
7590         if(oldEP & EP_BEROLIN_A) {
7591                 captured = board[fromY][fromX-1];
7592                 board[fromY][fromX-1] = EmptySquare;
7593         }else{  captured = board[fromY][fromX+1];
7594                 board[fromY][fromX+1] = EmptySquare;
7595         }
7596     } else if (board[fromY][fromX] == king
7597         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7598                && toY == fromY && toX > fromX+1) {
7599         board[fromY][fromX] = EmptySquare;
7600         board[toY][toX] = king;
7601         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7602         board[fromY][BOARD_RGHT-1] = EmptySquare;
7603     } else if (board[fromY][fromX] == king
7604         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7605                && toY == fromY && toX < fromX-1) {
7606         board[fromY][fromX] = EmptySquare;
7607         board[toY][toX] = king;
7608         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7609         board[fromY][BOARD_LEFT] = EmptySquare;
7610     } else if (fromY == 7 && fromX == 3
7611                && board[fromY][fromX] == BlackKing
7612                && toY == 7 && toX == 5) {
7613         board[fromY][fromX] = EmptySquare;
7614         board[toY][toX] = BlackKing;
7615         board[fromY][7] = EmptySquare;
7616         board[toY][4] = BlackRook;
7617     } else if (fromY == 7 && fromX == 3
7618                && board[fromY][fromX] == BlackKing
7619                && toY == 7 && toX == 1) {
7620         board[fromY][fromX] = EmptySquare;
7621         board[toY][toX] = BlackKing;
7622         board[fromY][0] = EmptySquare;
7623         board[toY][2] = BlackRook;
7624     } else if (board[fromY][fromX] == BlackPawn
7625                && toY == 0
7626                && gameInfo.variant != VariantXiangqi
7627                ) {
7628         /* black pawn promotion */
7629         board[0][toX] = CharToPiece(ToLower(promoChar));
7630         if (board[0][toX] == EmptySquare) {
7631             board[0][toX] = BlackQueen;
7632         }
7633         if(gameInfo.variant==VariantBughouse ||
7634            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7635             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7636         board[fromY][fromX] = EmptySquare;
7637     } else if ((fromY == 3)
7638                && (toX != fromX)
7639                && gameInfo.variant != VariantXiangqi
7640                && gameInfo.variant != VariantBerolina
7641                && (board[fromY][fromX] == BlackPawn)
7642                && (board[toY][toX] == EmptySquare)) {
7643         board[fromY][fromX] = EmptySquare;
7644         board[toY][toX] = BlackPawn;
7645         captured = board[toY + 1][toX];
7646         board[toY + 1][toX] = EmptySquare;
7647     } else if ((fromY == 3)
7648                && (toX == fromX)
7649                && gameInfo.variant == VariantBerolina
7650                && (board[fromY][fromX] == BlackPawn)
7651                && (board[toY][toX] == EmptySquare)) {
7652         board[fromY][fromX] = EmptySquare;
7653         board[toY][toX] = BlackPawn;
7654         if(oldEP & EP_BEROLIN_A) {
7655                 captured = board[fromY][fromX-1];
7656                 board[fromY][fromX-1] = EmptySquare;
7657         }else{  captured = board[fromY][fromX+1];
7658                 board[fromY][fromX+1] = EmptySquare;
7659         }
7660     } else {
7661         board[toY][toX] = board[fromY][fromX];
7662         board[fromY][fromX] = EmptySquare;
7663     }
7664
7665     /* [HGM] now we promote for Shogi, if needed */
7666     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7667         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7668   }
7669
7670     if (gameInfo.holdingsWidth != 0) {
7671
7672       /* !!A lot more code needs to be written to support holdings  */
7673       /* [HGM] OK, so I have written it. Holdings are stored in the */
7674       /* penultimate board files, so they are automaticlly stored   */
7675       /* in the game history.                                       */
7676       if (fromY == DROP_RANK) {
7677         /* Delete from holdings, by decreasing count */
7678         /* and erasing image if necessary            */
7679         p = (int) fromX;
7680         if(p < (int) BlackPawn) { /* white drop */
7681              p -= (int)WhitePawn;
7682                  p = PieceToNumber((ChessSquare)p);
7683              if(p >= gameInfo.holdingsSize) p = 0;
7684              if(--board[p][BOARD_WIDTH-2] <= 0)
7685                   board[p][BOARD_WIDTH-1] = EmptySquare;
7686              if((int)board[p][BOARD_WIDTH-2] < 0)
7687                         board[p][BOARD_WIDTH-2] = 0;
7688         } else {                  /* black drop */
7689              p -= (int)BlackPawn;
7690                  p = PieceToNumber((ChessSquare)p);
7691              if(p >= gameInfo.holdingsSize) p = 0;
7692              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7693                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7694              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7695                         board[BOARD_HEIGHT-1-p][1] = 0;
7696         }
7697       }
7698       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7699           && gameInfo.variant != VariantBughouse        ) {
7700         /* [HGM] holdings: Add to holdings, if holdings exist */
7701         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7702                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7703                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7704         }
7705         p = (int) captured;
7706         if (p >= (int) BlackPawn) {
7707           p -= (int)BlackPawn;
7708           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7709                   /* in Shogi restore piece to its original  first */
7710                   captured = (ChessSquare) (DEMOTED captured);
7711                   p = DEMOTED p;
7712           }
7713           p = PieceToNumber((ChessSquare)p);
7714           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7715           board[p][BOARD_WIDTH-2]++;
7716           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7717         } else {
7718           p -= (int)WhitePawn;
7719           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7720                   captured = (ChessSquare) (DEMOTED captured);
7721                   p = DEMOTED p;
7722           }
7723           p = PieceToNumber((ChessSquare)p);
7724           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7725           board[BOARD_HEIGHT-1-p][1]++;
7726           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7727         }
7728       }
7729     } else if (gameInfo.variant == VariantAtomic) {
7730       if (captured != EmptySquare) {
7731         int y, x;
7732         for (y = toY-1; y <= toY+1; y++) {
7733           for (x = toX-1; x <= toX+1; x++) {
7734             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7735                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7736               board[y][x] = EmptySquare;
7737             }
7738           }
7739         }
7740         board[toY][toX] = EmptySquare;
7741       }
7742     }
7743     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7744         /* [HGM] Shogi promotions */
7745         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7746     }
7747
7748     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7749                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7750         // [HGM] superchess: take promotion piece out of holdings
7751         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7752         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7753             if(!--board[k][BOARD_WIDTH-2])
7754                 board[k][BOARD_WIDTH-1] = EmptySquare;
7755         } else {
7756             if(!--board[BOARD_HEIGHT-1-k][1])
7757                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7758         }
7759     }
7760
7761 }
7762
7763 /* Updates forwardMostMove */
7764 void
7765 MakeMove(fromX, fromY, toX, toY, promoChar)
7766      int fromX, fromY, toX, toY;
7767      int promoChar;
7768 {
7769 //    forwardMostMove++; // [HGM] bare: moved downstream
7770
7771     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7772         int timeLeft; static int lastLoadFlag=0; int king, piece;
7773         piece = boards[forwardMostMove][fromY][fromX];
7774         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7775         if(gameInfo.variant == VariantKnightmate)
7776             king += (int) WhiteUnicorn - (int) WhiteKing;
7777         if(forwardMostMove == 0) {
7778             if(blackPlaysFirst) 
7779                 fprintf(serverMoves, "%s;", second.tidy);
7780             fprintf(serverMoves, "%s;", first.tidy);
7781             if(!blackPlaysFirst) 
7782                 fprintf(serverMoves, "%s;", second.tidy);
7783         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7784         lastLoadFlag = loadFlag;
7785         // print base move
7786         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7787         // print castling suffix
7788         if( toY == fromY && piece == king ) {
7789             if(toX-fromX > 1)
7790                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7791             if(fromX-toX >1)
7792                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7793         }
7794         // e.p. suffix
7795         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7796              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7797              boards[forwardMostMove][toY][toX] == EmptySquare
7798              && fromX != toX )
7799                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7800         // promotion suffix
7801         if(promoChar != NULLCHAR)
7802                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7803         if(!loadFlag) {
7804             fprintf(serverMoves, "/%d/%d",
7805                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7806             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7807             else                      timeLeft = blackTimeRemaining/1000;
7808             fprintf(serverMoves, "/%d", timeLeft);
7809         }
7810         fflush(serverMoves);
7811     }
7812
7813     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7814       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7815                         0, 1);
7816       return;
7817     }
7818     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7819     if (commentList[forwardMostMove+1] != NULL) {
7820         free(commentList[forwardMostMove+1]);
7821         commentList[forwardMostMove+1] = NULL;
7822     }
7823     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7824     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7825     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7826     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7827     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7828     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7829     gameInfo.result = GameUnfinished;
7830     if (gameInfo.resultDetails != NULL) {
7831         free(gameInfo.resultDetails);
7832         gameInfo.resultDetails = NULL;
7833     }
7834     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7835                               moveList[forwardMostMove - 1]);
7836     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7837                              PosFlags(forwardMostMove - 1),
7838                              fromY, fromX, toY, toX, promoChar,
7839                              parseList[forwardMostMove - 1]);
7840     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7841       case MT_NONE:
7842       case MT_STALEMATE:
7843       default:
7844         break;
7845       case MT_CHECK:
7846         if(gameInfo.variant != VariantShogi)
7847             strcat(parseList[forwardMostMove - 1], "+");
7848         break;
7849       case MT_CHECKMATE:
7850       case MT_STAINMATE:
7851         strcat(parseList[forwardMostMove - 1], "#");
7852         break;
7853     }
7854     if (appData.debugMode) {
7855         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7856     }
7857
7858 }
7859
7860 /* Updates currentMove if not pausing */
7861 void
7862 ShowMove(fromX, fromY, toX, toY)
7863 {
7864     int instant = (gameMode == PlayFromGameFile) ?
7865         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7866     if(appData.noGUI) return;
7867     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7868         if (!instant) {
7869             if (forwardMostMove == currentMove + 1) {
7870                 AnimateMove(boards[forwardMostMove - 1],
7871                             fromX, fromY, toX, toY);
7872             }
7873             if (appData.highlightLastMove) {
7874                 SetHighlights(fromX, fromY, toX, toY);
7875             }
7876         }
7877         currentMove = forwardMostMove;
7878     }
7879
7880     if (instant) return;
7881
7882     DisplayMove(currentMove - 1);
7883     DrawPosition(FALSE, boards[currentMove]);
7884     DisplayBothClocks();
7885     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7886 }
7887
7888 void SendEgtPath(ChessProgramState *cps)
7889 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7890         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7891
7892         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7893
7894         while(*p) {
7895             char c, *q = name+1, *r, *s;
7896
7897             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7898             while(*p && *p != ',') *q++ = *p++;
7899             *q++ = ':'; *q = 0;
7900             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7901                 strcmp(name, ",nalimov:") == 0 ) {
7902                 // take nalimov path from the menu-changeable option first, if it is defined
7903                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7904                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7905             } else
7906             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7907                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7908                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7909                 s = r = StrStr(s, ":") + 1; // beginning of path info
7910                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7911                 c = *r; *r = 0;             // temporarily null-terminate path info
7912                     *--q = 0;               // strip of trailig ':' from name
7913                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7914                 *r = c;
7915                 SendToProgram(buf,cps);     // send egtbpath command for this format
7916             }
7917             if(*p == ',') p++; // read away comma to position for next format name
7918         }
7919 }
7920
7921 void
7922 InitChessProgram(cps, setup)
7923      ChessProgramState *cps;
7924      int setup; /* [HGM] needed to setup FRC opening position */
7925 {
7926     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7927     if (appData.noChessProgram) return;
7928     hintRequested = FALSE;
7929     bookRequested = FALSE;
7930
7931     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7932     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7933     if(cps->memSize) { /* [HGM] memory */
7934         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7935         SendToProgram(buf, cps);
7936     }
7937     SendEgtPath(cps); /* [HGM] EGT */
7938     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7939         sprintf(buf, "cores %d\n", appData.smpCores);
7940         SendToProgram(buf, cps);
7941     }
7942
7943     SendToProgram(cps->initString, cps);
7944     if (gameInfo.variant != VariantNormal &&
7945         gameInfo.variant != VariantLoadable
7946         /* [HGM] also send variant if board size non-standard */
7947         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7948                                             ) {
7949       char *v = VariantName(gameInfo.variant);
7950       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7951         /* [HGM] in protocol 1 we have to assume all variants valid */
7952         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7953         DisplayFatalError(buf, 0, 1);
7954         return;
7955       }
7956
7957       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7958       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7959       if( gameInfo.variant == VariantXiangqi )
7960            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7961       if( gameInfo.variant == VariantShogi )
7962            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7963       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7964            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7965       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7966                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7967            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7968       if( gameInfo.variant == VariantCourier )
7969            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7970       if( gameInfo.variant == VariantSuper )
7971            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7972       if( gameInfo.variant == VariantGreat )
7973            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7974
7975       if(overruled) {
7976            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7977                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7978            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7979            if(StrStr(cps->variants, b) == NULL) { 
7980                // specific sized variant not known, check if general sizing allowed
7981                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7982                    if(StrStr(cps->variants, "boardsize") == NULL) {
7983                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7984                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7985                        DisplayFatalError(buf, 0, 1);
7986                        return;
7987                    }
7988                    /* [HGM] here we really should compare with the maximum supported board size */
7989                }
7990            }
7991       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7992       sprintf(buf, "variant %s\n", b);
7993       SendToProgram(buf, cps);
7994     }
7995     currentlyInitializedVariant = gameInfo.variant;
7996
7997     /* [HGM] send opening position in FRC to first engine */
7998     if(setup) {
7999           SendToProgram("force\n", cps);
8000           SendBoard(cps, 0);
8001           /* engine is now in force mode! Set flag to wake it up after first move. */
8002           setboardSpoiledMachineBlack = 1;
8003     }
8004
8005     if (cps->sendICS) {
8006       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8007       SendToProgram(buf, cps);
8008     }
8009     cps->maybeThinking = FALSE;
8010     cps->offeredDraw = 0;
8011     if (!appData.icsActive) {
8012         SendTimeControl(cps, movesPerSession, timeControl,
8013                         timeIncrement, appData.searchDepth,
8014                         searchTime);
8015     }
8016     if (appData.showThinking 
8017         // [HGM] thinking: four options require thinking output to be sent
8018         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8019                                 ) {
8020         SendToProgram("post\n", cps);
8021     }
8022     SendToProgram("hard\n", cps);
8023     if (!appData.ponderNextMove) {
8024         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8025            it without being sure what state we are in first.  "hard"
8026            is not a toggle, so that one is OK.
8027          */
8028         SendToProgram("easy\n", cps);
8029     }
8030     if (cps->usePing) {
8031       sprintf(buf, "ping %d\n", ++cps->lastPing);
8032       SendToProgram(buf, cps);
8033     }
8034     cps->initDone = TRUE;
8035 }   
8036
8037
8038 void
8039 StartChessProgram(cps)
8040      ChessProgramState *cps;
8041 {
8042     char buf[MSG_SIZ];
8043     int err;
8044
8045     if (appData.noChessProgram) return;
8046     cps->initDone = FALSE;
8047
8048     if (strcmp(cps->host, "localhost") == 0) {
8049         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8050     } else if (*appData.remoteShell == NULLCHAR) {
8051         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8052     } else {
8053         if (*appData.remoteUser == NULLCHAR) {
8054           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8055                     cps->program);
8056         } else {
8057           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8058                     cps->host, appData.remoteUser, cps->program);
8059         }
8060         err = StartChildProcess(buf, "", &cps->pr);
8061     }
8062     
8063     if (err != 0) {
8064         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8065         DisplayFatalError(buf, err, 1);
8066         cps->pr = NoProc;
8067         cps->isr = NULL;
8068         return;
8069     }
8070     
8071     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8072     if (cps->protocolVersion > 1) {
8073       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8074       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8075       cps->comboCnt = 0;  //                and values of combo boxes
8076       SendToProgram(buf, cps);
8077     } else {
8078       SendToProgram("xboard\n", cps);
8079     }
8080 }
8081
8082
8083 void
8084 TwoMachinesEventIfReady P((void))
8085 {
8086   if (first.lastPing != first.lastPong) {
8087     DisplayMessage("", _("Waiting for first chess program"));
8088     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8089     return;
8090   }
8091   if (second.lastPing != second.lastPong) {
8092     DisplayMessage("", _("Waiting for second chess program"));
8093     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8094     return;
8095   }
8096   ThawUI();
8097   TwoMachinesEvent();
8098 }
8099
8100 void
8101 NextMatchGame P((void))
8102 {
8103     int index; /* [HGM] autoinc: step load index during match */
8104     Reset(FALSE, TRUE);
8105     if (*appData.loadGameFile != NULLCHAR) {
8106         index = appData.loadGameIndex;
8107         if(index < 0) { // [HGM] autoinc
8108             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8109             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8110         } 
8111         LoadGameFromFile(appData.loadGameFile,
8112                          index,
8113                          appData.loadGameFile, FALSE);
8114     } else if (*appData.loadPositionFile != NULLCHAR) {
8115         index = appData.loadPositionIndex;
8116         if(index < 0) { // [HGM] autoinc
8117             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8118             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8119         } 
8120         LoadPositionFromFile(appData.loadPositionFile,
8121                              index,
8122                              appData.loadPositionFile);
8123     }
8124     TwoMachinesEventIfReady();
8125 }
8126
8127 void UserAdjudicationEvent( int result )
8128 {
8129     ChessMove gameResult = GameIsDrawn;
8130
8131     if( result > 0 ) {
8132         gameResult = WhiteWins;
8133     }
8134     else if( result < 0 ) {
8135         gameResult = BlackWins;
8136     }
8137
8138     if( gameMode == TwoMachinesPlay ) {
8139         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8140     }
8141 }
8142
8143
8144 // [HGM] save: calculate checksum of game to make games easily identifiable
8145 int StringCheckSum(char *s)
8146 {
8147         int i = 0;
8148         if(s==NULL) return 0;
8149         while(*s) i = i*259 + *s++;
8150         return i;
8151 }
8152
8153 int GameCheckSum()
8154 {
8155         int i, sum=0;
8156         for(i=backwardMostMove; i<forwardMostMove; i++) {
8157                 sum += pvInfoList[i].depth;
8158                 sum += StringCheckSum(parseList[i]);
8159                 sum += StringCheckSum(commentList[i]);
8160                 sum *= 261;
8161         }
8162         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8163         return sum + StringCheckSum(commentList[i]);
8164 } // end of save patch
8165
8166 void
8167 GameEnds(result, resultDetails, whosays)
8168      ChessMove result;
8169      char *resultDetails;
8170      int whosays;
8171 {
8172     GameMode nextGameMode;
8173     int isIcsGame;
8174     char buf[MSG_SIZ];
8175
8176     if(endingGame) return; /* [HGM] crash: forbid recursion */
8177     endingGame = 1;
8178
8179     if (appData.debugMode) {
8180       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8181               result, resultDetails ? resultDetails : "(null)", whosays);
8182     }
8183
8184     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8185         /* If we are playing on ICS, the server decides when the
8186            game is over, but the engine can offer to draw, claim 
8187            a draw, or resign. 
8188          */
8189 #if ZIPPY
8190         if (appData.zippyPlay && first.initDone) {
8191             if (result == GameIsDrawn) {
8192                 /* In case draw still needs to be claimed */
8193                 SendToICS(ics_prefix);
8194                 SendToICS("draw\n");
8195             } else if (StrCaseStr(resultDetails, "resign")) {
8196                 SendToICS(ics_prefix);
8197                 SendToICS("resign\n");
8198             }
8199         }
8200 #endif
8201         endingGame = 0; /* [HGM] crash */
8202         return;
8203     }
8204
8205     /* If we're loading the game from a file, stop */
8206     if (whosays == GE_FILE) {
8207       (void) StopLoadGameTimer();
8208       gameFileFP = NULL;
8209     }
8210
8211     /* Cancel draw offers */
8212     first.offeredDraw = second.offeredDraw = 0;
8213
8214     /* If this is an ICS game, only ICS can really say it's done;
8215        if not, anyone can. */
8216     isIcsGame = (gameMode == IcsPlayingWhite || 
8217                  gameMode == IcsPlayingBlack || 
8218                  gameMode == IcsObserving    || 
8219                  gameMode == IcsExamining);
8220
8221     if (!isIcsGame || whosays == GE_ICS) {
8222         /* OK -- not an ICS game, or ICS said it was done */
8223         StopClocks();
8224         if (!isIcsGame && !appData.noChessProgram) 
8225           SetUserThinkingEnables();
8226     
8227         /* [HGM] if a machine claims the game end we verify this claim */
8228         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8229             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8230                 char claimer;
8231                 ChessMove trueResult = (ChessMove) -1;
8232
8233                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8234                                             first.twoMachinesColor[0] :
8235                                             second.twoMachinesColor[0] ;
8236
8237                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8238                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8239                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8240                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8241                 } else
8242                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8243                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8244                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8245                 } else
8246                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8247                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8248                 }
8249
8250                 // now verify win claims, but not in drop games, as we don't understand those yet
8251                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8252                                                  || gameInfo.variant == VariantGreat) &&
8253                     (result == WhiteWins && claimer == 'w' ||
8254                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8255                       if (appData.debugMode) {
8256                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8257                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8258                       }
8259                       if(result != trueResult) {
8260                               sprintf(buf, "False win claim: '%s'", resultDetails);
8261                               result = claimer == 'w' ? BlackWins : WhiteWins;
8262                               resultDetails = buf;
8263                       }
8264                 } else
8265                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8266                     && (forwardMostMove <= backwardMostMove ||
8267                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8268                         (claimer=='b')==(forwardMostMove&1))
8269                                                                                   ) {
8270                       /* [HGM] verify: draws that were not flagged are false claims */
8271                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8272                       result = claimer == 'w' ? BlackWins : WhiteWins;
8273                       resultDetails = buf;
8274                 }
8275                 /* (Claiming a loss is accepted no questions asked!) */
8276             }
8277             /* [HGM] bare: don't allow bare King to win */
8278             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8279                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8280                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8281                && result != GameIsDrawn)
8282             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8283                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8284                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8285                         if(p >= 0 && p <= (int)WhiteKing) k++;
8286                 }
8287                 if (appData.debugMode) {
8288                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8289                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8290                 }
8291                 if(k <= 1) {
8292                         result = GameIsDrawn;
8293                         sprintf(buf, "%s but bare king", resultDetails);
8294                         resultDetails = buf;
8295                 }
8296             }
8297         }
8298
8299
8300         if(serverMoves != NULL && !loadFlag) { char c = '=';
8301             if(result==WhiteWins) c = '+';
8302             if(result==BlackWins) c = '-';
8303             if(resultDetails != NULL)
8304                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8305         }
8306         if (resultDetails != NULL) {
8307             gameInfo.result = result;
8308             gameInfo.resultDetails = StrSave(resultDetails);
8309
8310             /* display last move only if game was not loaded from file */
8311             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8312                 DisplayMove(currentMove - 1);
8313     
8314             if (forwardMostMove != 0) {
8315                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8316                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8317                                                                 ) {
8318                     if (*appData.saveGameFile != NULLCHAR) {
8319                         SaveGameToFile(appData.saveGameFile, TRUE);
8320                     } else if (appData.autoSaveGames) {
8321                         AutoSaveGame();
8322                     }
8323                     if (*appData.savePositionFile != NULLCHAR) {
8324                         SavePositionToFile(appData.savePositionFile);
8325                     }
8326                 }
8327             }
8328
8329             /* Tell program how game ended in case it is learning */
8330             /* [HGM] Moved this to after saving the PGN, just in case */
8331             /* engine died and we got here through time loss. In that */
8332             /* case we will get a fatal error writing the pipe, which */
8333             /* would otherwise lose us the PGN.                       */
8334             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8335             /* output during GameEnds should never be fatal anymore   */
8336             if (gameMode == MachinePlaysWhite ||
8337                 gameMode == MachinePlaysBlack ||
8338                 gameMode == TwoMachinesPlay ||
8339                 gameMode == IcsPlayingWhite ||
8340                 gameMode == IcsPlayingBlack ||
8341                 gameMode == BeginningOfGame) {
8342                 char buf[MSG_SIZ];
8343                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8344                         resultDetails);
8345                 if (first.pr != NoProc) {
8346                     SendToProgram(buf, &first);
8347                 }
8348                 if (second.pr != NoProc &&
8349                     gameMode == TwoMachinesPlay) {
8350                     SendToProgram(buf, &second);
8351                 }
8352             }
8353         }
8354
8355         if (appData.icsActive) {
8356             if (appData.quietPlay &&
8357                 (gameMode == IcsPlayingWhite ||
8358                  gameMode == IcsPlayingBlack)) {
8359                 SendToICS(ics_prefix);
8360                 SendToICS("set shout 1\n");
8361             }
8362             nextGameMode = IcsIdle;
8363             ics_user_moved = FALSE;
8364             /* clean up premove.  It's ugly when the game has ended and the
8365              * premove highlights are still on the board.
8366              */
8367             if (gotPremove) {
8368               gotPremove = FALSE;
8369               ClearPremoveHighlights();
8370               DrawPosition(FALSE, boards[currentMove]);
8371             }
8372             if (whosays == GE_ICS) {
8373                 switch (result) {
8374                 case WhiteWins:
8375                     if (gameMode == IcsPlayingWhite)
8376                         PlayIcsWinSound();
8377                     else if(gameMode == IcsPlayingBlack)
8378                         PlayIcsLossSound();
8379                     break;
8380                 case BlackWins:
8381                     if (gameMode == IcsPlayingBlack)
8382                         PlayIcsWinSound();
8383                     else if(gameMode == IcsPlayingWhite)
8384                         PlayIcsLossSound();
8385                     break;
8386                 case GameIsDrawn:
8387                     PlayIcsDrawSound();
8388                     break;
8389                 default:
8390                     PlayIcsUnfinishedSound();
8391                 }
8392             }
8393         } else if (gameMode == EditGame ||
8394                    gameMode == PlayFromGameFile || 
8395                    gameMode == AnalyzeMode || 
8396                    gameMode == AnalyzeFile) {
8397             nextGameMode = gameMode;
8398         } else {
8399             nextGameMode = EndOfGame;
8400         }
8401         pausing = FALSE;
8402         ModeHighlight();
8403     } else {
8404         nextGameMode = gameMode;
8405     }
8406
8407     if (appData.noChessProgram) {
8408         gameMode = nextGameMode;
8409         ModeHighlight();
8410         endingGame = 0; /* [HGM] crash */
8411         return;
8412     }
8413
8414     if (first.reuse) {
8415         /* Put first chess program into idle state */
8416         if (first.pr != NoProc &&
8417             (gameMode == MachinePlaysWhite ||
8418              gameMode == MachinePlaysBlack ||
8419              gameMode == TwoMachinesPlay ||
8420              gameMode == IcsPlayingWhite ||
8421              gameMode == IcsPlayingBlack ||
8422              gameMode == BeginningOfGame)) {
8423             SendToProgram("force\n", &first);
8424             if (first.usePing) {
8425               char buf[MSG_SIZ];
8426               sprintf(buf, "ping %d\n", ++first.lastPing);
8427               SendToProgram(buf, &first);
8428             }
8429         }
8430     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8431         /* Kill off first chess program */
8432         if (first.isr != NULL)
8433           RemoveInputSource(first.isr);
8434         first.isr = NULL;
8435     
8436         if (first.pr != NoProc) {
8437             ExitAnalyzeMode();
8438             DoSleep( appData.delayBeforeQuit );
8439             SendToProgram("quit\n", &first);
8440             DoSleep( appData.delayAfterQuit );
8441             DestroyChildProcess(first.pr, first.useSigterm);
8442         }
8443         first.pr = NoProc;
8444     }
8445     if (second.reuse) {
8446         /* Put second chess program into idle state */
8447         if (second.pr != NoProc &&
8448             gameMode == TwoMachinesPlay) {
8449             SendToProgram("force\n", &second);
8450             if (second.usePing) {
8451               char buf[MSG_SIZ];
8452               sprintf(buf, "ping %d\n", ++second.lastPing);
8453               SendToProgram(buf, &second);
8454             }
8455         }
8456     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8457         /* Kill off second chess program */
8458         if (second.isr != NULL)
8459           RemoveInputSource(second.isr);
8460         second.isr = NULL;
8461     
8462         if (second.pr != NoProc) {
8463             DoSleep( appData.delayBeforeQuit );
8464             SendToProgram("quit\n", &second);
8465             DoSleep( appData.delayAfterQuit );
8466             DestroyChildProcess(second.pr, second.useSigterm);
8467         }
8468         second.pr = NoProc;
8469     }
8470
8471     if (matchMode && gameMode == TwoMachinesPlay) {
8472         switch (result) {
8473         case WhiteWins:
8474           if (first.twoMachinesColor[0] == 'w') {
8475             first.matchWins++;
8476           } else {
8477             second.matchWins++;
8478           }
8479           break;
8480         case BlackWins:
8481           if (first.twoMachinesColor[0] == 'b') {
8482             first.matchWins++;
8483           } else {
8484             second.matchWins++;
8485           }
8486           break;
8487         default:
8488           break;
8489         }
8490         if (matchGame < appData.matchGames) {
8491             char *tmp;
8492             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8493                 tmp = first.twoMachinesColor;
8494                 first.twoMachinesColor = second.twoMachinesColor;
8495                 second.twoMachinesColor = tmp;
8496             }
8497             gameMode = nextGameMode;
8498             matchGame++;
8499             if(appData.matchPause>10000 || appData.matchPause<10)
8500                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8501             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8502             endingGame = 0; /* [HGM] crash */
8503             return;
8504         } else {
8505             char buf[MSG_SIZ];
8506             gameMode = nextGameMode;
8507             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8508                     first.tidy, second.tidy,
8509                     first.matchWins, second.matchWins,
8510                     appData.matchGames - (first.matchWins + second.matchWins));
8511             DisplayFatalError(buf, 0, 0);
8512         }
8513     }
8514     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8515         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8516       ExitAnalyzeMode();
8517     gameMode = nextGameMode;
8518     ModeHighlight();
8519     endingGame = 0;  /* [HGM] crash */
8520 }
8521
8522 /* Assumes program was just initialized (initString sent).
8523    Leaves program in force mode. */
8524 void
8525 FeedMovesToProgram(cps, upto) 
8526      ChessProgramState *cps;
8527      int upto;
8528 {
8529     int i;
8530     
8531     if (appData.debugMode)
8532       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8533               startedFromSetupPosition ? "position and " : "",
8534               backwardMostMove, upto, cps->which);
8535     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8536         // [HGM] variantswitch: make engine aware of new variant
8537         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8538                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8539         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8540         SendToProgram(buf, cps);
8541         currentlyInitializedVariant = gameInfo.variant;
8542     }
8543     SendToProgram("force\n", cps);
8544     if (startedFromSetupPosition) {
8545         SendBoard(cps, backwardMostMove);
8546     if (appData.debugMode) {
8547         fprintf(debugFP, "feedMoves\n");
8548     }
8549     }
8550     for (i = backwardMostMove; i < upto; i++) {
8551         SendMoveToProgram(i, cps);
8552     }
8553 }
8554
8555
8556 void
8557 ResurrectChessProgram()
8558 {
8559      /* The chess program may have exited.
8560         If so, restart it and feed it all the moves made so far. */
8561
8562     if (appData.noChessProgram || first.pr != NoProc) return;
8563     
8564     StartChessProgram(&first);
8565     InitChessProgram(&first, FALSE);
8566     FeedMovesToProgram(&first, currentMove);
8567
8568     if (!first.sendTime) {
8569         /* can't tell gnuchess what its clock should read,
8570            so we bow to its notion. */
8571         ResetClocks();
8572         timeRemaining[0][currentMove] = whiteTimeRemaining;
8573         timeRemaining[1][currentMove] = blackTimeRemaining;
8574     }
8575
8576     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8577                 appData.icsEngineAnalyze) && first.analysisSupport) {
8578       SendToProgram("analyze\n", &first);
8579       first.analyzing = TRUE;
8580     }
8581 }
8582
8583 /*
8584  * Button procedures
8585  */
8586 void
8587 Reset(redraw, init)
8588      int redraw, init;
8589 {
8590     int i;
8591
8592     if (appData.debugMode) {
8593         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8594                 redraw, init, gameMode);
8595     }
8596     CleanupTail(); // [HGM] vari: delete any stored variations
8597     pausing = pauseExamInvalid = FALSE;
8598     startedFromSetupPosition = blackPlaysFirst = FALSE;
8599     firstMove = TRUE;
8600     whiteFlag = blackFlag = FALSE;
8601     userOfferedDraw = FALSE;
8602     hintRequested = bookRequested = FALSE;
8603     first.maybeThinking = FALSE;
8604     second.maybeThinking = FALSE;
8605     first.bookSuspend = FALSE; // [HGM] book
8606     second.bookSuspend = FALSE;
8607     thinkOutput[0] = NULLCHAR;
8608     lastHint[0] = NULLCHAR;
8609     ClearGameInfo(&gameInfo);
8610     gameInfo.variant = StringToVariant(appData.variant);
8611     ics_user_moved = ics_clock_paused = FALSE;
8612     ics_getting_history = H_FALSE;
8613     ics_gamenum = -1;
8614     white_holding[0] = black_holding[0] = NULLCHAR;
8615     ClearProgramStats();
8616     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8617     
8618     ResetFrontEnd();
8619     ClearHighlights();
8620     flipView = appData.flipView;
8621     ClearPremoveHighlights();
8622     gotPremove = FALSE;
8623     alarmSounded = FALSE;
8624
8625     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8626     if(appData.serverMovesName != NULL) {
8627         /* [HGM] prepare to make moves file for broadcasting */
8628         clock_t t = clock();
8629         if(serverMoves != NULL) fclose(serverMoves);
8630         serverMoves = fopen(appData.serverMovesName, "r");
8631         if(serverMoves != NULL) {
8632             fclose(serverMoves);
8633             /* delay 15 sec before overwriting, so all clients can see end */
8634             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8635         }
8636         serverMoves = fopen(appData.serverMovesName, "w");
8637     }
8638
8639     ExitAnalyzeMode();
8640     gameMode = BeginningOfGame;
8641     ModeHighlight();
8642     if(appData.icsActive) gameInfo.variant = VariantNormal;
8643     currentMove = forwardMostMove = backwardMostMove = 0;
8644     InitPosition(redraw);
8645     for (i = 0; i < MAX_MOVES; i++) {
8646         if (commentList[i] != NULL) {
8647             free(commentList[i]);
8648             commentList[i] = NULL;
8649         }
8650     }
8651     ResetClocks();
8652     timeRemaining[0][0] = whiteTimeRemaining;
8653     timeRemaining[1][0] = blackTimeRemaining;
8654     if (first.pr == NULL) {
8655         StartChessProgram(&first);
8656     }
8657     if (init) {
8658             InitChessProgram(&first, startedFromSetupPosition);
8659     }
8660     DisplayTitle("");
8661     DisplayMessage("", "");
8662     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8663     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8664 }
8665
8666 void
8667 AutoPlayGameLoop()
8668 {
8669     for (;;) {
8670         if (!AutoPlayOneMove())
8671           return;
8672         if (matchMode || appData.timeDelay == 0)
8673           continue;
8674         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8675           return;
8676         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8677         break;
8678     }
8679 }
8680
8681
8682 int
8683 AutoPlayOneMove()
8684 {
8685     int fromX, fromY, toX, toY;
8686
8687     if (appData.debugMode) {
8688       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8689     }
8690
8691     if (gameMode != PlayFromGameFile)
8692       return FALSE;
8693
8694     if (currentMove >= forwardMostMove) {
8695       gameMode = EditGame;
8696       ModeHighlight();
8697
8698       /* [AS] Clear current move marker at the end of a game */
8699       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8700
8701       return FALSE;
8702     }
8703     
8704     toX = moveList[currentMove][2] - AAA;
8705     toY = moveList[currentMove][3] - ONE;
8706
8707     if (moveList[currentMove][1] == '@') {
8708         if (appData.highlightLastMove) {
8709             SetHighlights(-1, -1, toX, toY);
8710         }
8711     } else {
8712         fromX = moveList[currentMove][0] - AAA;
8713         fromY = moveList[currentMove][1] - ONE;
8714
8715         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8716
8717         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8718
8719         if (appData.highlightLastMove) {
8720             SetHighlights(fromX, fromY, toX, toY);
8721         }
8722     }
8723     DisplayMove(currentMove);
8724     SendMoveToProgram(currentMove++, &first);
8725     DisplayBothClocks();
8726     DrawPosition(FALSE, boards[currentMove]);
8727     // [HGM] PV info: always display, routine tests if empty
8728     DisplayComment(currentMove - 1, commentList[currentMove]);
8729     return TRUE;
8730 }
8731
8732
8733 int
8734 LoadGameOneMove(readAhead)
8735      ChessMove readAhead;
8736 {
8737     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8738     char promoChar = NULLCHAR;
8739     ChessMove moveType;
8740     char move[MSG_SIZ];
8741     char *p, *q;
8742     
8743     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8744         gameMode != AnalyzeMode && gameMode != Training) {
8745         gameFileFP = NULL;
8746         return FALSE;
8747     }
8748     
8749     yyboardindex = forwardMostMove;
8750     if (readAhead != (ChessMove)0) {
8751       moveType = readAhead;
8752     } else {
8753       if (gameFileFP == NULL)
8754           return FALSE;
8755       moveType = (ChessMove) yylex();
8756     }
8757     
8758     done = FALSE;
8759     switch (moveType) {
8760       case Comment:
8761         if (appData.debugMode) 
8762           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8763         p = yy_text;
8764
8765         /* append the comment but don't display it */
8766         AppendComment(currentMove, p, FALSE);
8767         return TRUE;
8768
8769       case WhiteCapturesEnPassant:
8770       case BlackCapturesEnPassant:
8771       case WhitePromotionChancellor:
8772       case BlackPromotionChancellor:
8773       case WhitePromotionArchbishop:
8774       case BlackPromotionArchbishop:
8775       case WhitePromotionCentaur:
8776       case BlackPromotionCentaur:
8777       case WhitePromotionQueen:
8778       case BlackPromotionQueen:
8779       case WhitePromotionRook:
8780       case BlackPromotionRook:
8781       case WhitePromotionBishop:
8782       case BlackPromotionBishop:
8783       case WhitePromotionKnight:
8784       case BlackPromotionKnight:
8785       case WhitePromotionKing:
8786       case BlackPromotionKing:
8787       case NormalMove:
8788       case WhiteKingSideCastle:
8789       case WhiteQueenSideCastle:
8790       case BlackKingSideCastle:
8791       case BlackQueenSideCastle:
8792       case WhiteKingSideCastleWild:
8793       case WhiteQueenSideCastleWild:
8794       case BlackKingSideCastleWild:
8795       case BlackQueenSideCastleWild:
8796       /* PUSH Fabien */
8797       case WhiteHSideCastleFR:
8798       case WhiteASideCastleFR:
8799       case BlackHSideCastleFR:
8800       case BlackASideCastleFR:
8801       /* POP Fabien */
8802         if (appData.debugMode)
8803           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8804         fromX = currentMoveString[0] - AAA;
8805         fromY = currentMoveString[1] - ONE;
8806         toX = currentMoveString[2] - AAA;
8807         toY = currentMoveString[3] - ONE;
8808         promoChar = currentMoveString[4];
8809         break;
8810
8811       case WhiteDrop:
8812       case BlackDrop:
8813         if (appData.debugMode)
8814           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8815         fromX = moveType == WhiteDrop ?
8816           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8817         (int) CharToPiece(ToLower(currentMoveString[0]));
8818         fromY = DROP_RANK;
8819         toX = currentMoveString[2] - AAA;
8820         toY = currentMoveString[3] - ONE;
8821         break;
8822
8823       case WhiteWins:
8824       case BlackWins:
8825       case GameIsDrawn:
8826       case GameUnfinished:
8827         if (appData.debugMode)
8828           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8829         p = strchr(yy_text, '{');
8830         if (p == NULL) p = strchr(yy_text, '(');
8831         if (p == NULL) {
8832             p = yy_text;
8833             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8834         } else {
8835             q = strchr(p, *p == '{' ? '}' : ')');
8836             if (q != NULL) *q = NULLCHAR;
8837             p++;
8838         }
8839         GameEnds(moveType, p, GE_FILE);
8840         done = TRUE;
8841         if (cmailMsgLoaded) {
8842             ClearHighlights();
8843             flipView = WhiteOnMove(currentMove);
8844             if (moveType == GameUnfinished) flipView = !flipView;
8845             if (appData.debugMode)
8846               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8847         }
8848         break;
8849
8850       case (ChessMove) 0:       /* end of file */
8851         if (appData.debugMode)
8852           fprintf(debugFP, "Parser hit end of file\n");
8853         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8854           case MT_NONE:
8855           case MT_CHECK:
8856             break;
8857           case MT_CHECKMATE:
8858           case MT_STAINMATE:
8859             if (WhiteOnMove(currentMove)) {
8860                 GameEnds(BlackWins, "Black mates", GE_FILE);
8861             } else {
8862                 GameEnds(WhiteWins, "White mates", GE_FILE);
8863             }
8864             break;
8865           case MT_STALEMATE:
8866             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8867             break;
8868         }
8869         done = TRUE;
8870         break;
8871
8872       case MoveNumberOne:
8873         if (lastLoadGameStart == GNUChessGame) {
8874             /* GNUChessGames have numbers, but they aren't move numbers */
8875             if (appData.debugMode)
8876               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8877                       yy_text, (int) moveType);
8878             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8879         }
8880         /* else fall thru */
8881
8882       case XBoardGame:
8883       case GNUChessGame:
8884       case PGNTag:
8885         /* Reached start of next game in file */
8886         if (appData.debugMode)
8887           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8888         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8889           case MT_NONE:
8890           case MT_CHECK:
8891             break;
8892           case MT_CHECKMATE:
8893           case MT_STAINMATE:
8894             if (WhiteOnMove(currentMove)) {
8895                 GameEnds(BlackWins, "Black mates", GE_FILE);
8896             } else {
8897                 GameEnds(WhiteWins, "White mates", GE_FILE);
8898             }
8899             break;
8900           case MT_STALEMATE:
8901             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8902             break;
8903         }
8904         done = TRUE;
8905         break;
8906
8907       case PositionDiagram:     /* should not happen; ignore */
8908       case ElapsedTime:         /* ignore */
8909       case NAG:                 /* ignore */
8910         if (appData.debugMode)
8911           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8912                   yy_text, (int) moveType);
8913         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8914
8915       case IllegalMove:
8916         if (appData.testLegality) {
8917             if (appData.debugMode)
8918               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8919             sprintf(move, _("Illegal move: %d.%s%s"),
8920                     (forwardMostMove / 2) + 1,
8921                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8922             DisplayError(move, 0);
8923             done = TRUE;
8924         } else {
8925             if (appData.debugMode)
8926               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8927                       yy_text, currentMoveString);
8928             fromX = currentMoveString[0] - AAA;
8929             fromY = currentMoveString[1] - ONE;
8930             toX = currentMoveString[2] - AAA;
8931             toY = currentMoveString[3] - ONE;
8932             promoChar = currentMoveString[4];
8933         }
8934         break;
8935
8936       case AmbiguousMove:
8937         if (appData.debugMode)
8938           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8939         sprintf(move, _("Ambiguous move: %d.%s%s"),
8940                 (forwardMostMove / 2) + 1,
8941                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8942         DisplayError(move, 0);
8943         done = TRUE;
8944         break;
8945
8946       default:
8947       case ImpossibleMove:
8948         if (appData.debugMode)
8949           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8950         sprintf(move, _("Illegal move: %d.%s%s"),
8951                 (forwardMostMove / 2) + 1,
8952                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8953         DisplayError(move, 0);
8954         done = TRUE;
8955         break;
8956     }
8957
8958     if (done) {
8959         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8960             DrawPosition(FALSE, boards[currentMove]);
8961             DisplayBothClocks();
8962             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8963               DisplayComment(currentMove - 1, commentList[currentMove]);
8964         }
8965         (void) StopLoadGameTimer();
8966         gameFileFP = NULL;
8967         cmailOldMove = forwardMostMove;
8968         return FALSE;
8969     } else {
8970         /* currentMoveString is set as a side-effect of yylex */
8971         strcat(currentMoveString, "\n");
8972         strcpy(moveList[forwardMostMove], currentMoveString);
8973         
8974         thinkOutput[0] = NULLCHAR;
8975         MakeMove(fromX, fromY, toX, toY, promoChar);
8976         currentMove = forwardMostMove;
8977         return TRUE;
8978     }
8979 }
8980
8981 /* Load the nth game from the given file */
8982 int
8983 LoadGameFromFile(filename, n, title, useList)
8984      char *filename;
8985      int n;
8986      char *title;
8987      /*Boolean*/ int useList;
8988 {
8989     FILE *f;
8990     char buf[MSG_SIZ];
8991
8992     if (strcmp(filename, "-") == 0) {
8993         f = stdin;
8994         title = "stdin";
8995     } else {
8996         f = fopen(filename, "rb");
8997         if (f == NULL) {
8998           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8999             DisplayError(buf, errno);
9000             return FALSE;
9001         }
9002     }
9003     if (fseek(f, 0, 0) == -1) {
9004         /* f is not seekable; probably a pipe */
9005         useList = FALSE;
9006     }
9007     if (useList && n == 0) {
9008         int error = GameListBuild(f);
9009         if (error) {
9010             DisplayError(_("Cannot build game list"), error);
9011         } else if (!ListEmpty(&gameList) &&
9012                    ((ListGame *) gameList.tailPred)->number > 1) {
9013             GameListPopUp(f, title);
9014             return TRUE;
9015         }
9016         GameListDestroy();
9017         n = 1;
9018     }
9019     if (n == 0) n = 1;
9020     return LoadGame(f, n, title, FALSE);
9021 }
9022
9023
9024 void
9025 MakeRegisteredMove()
9026 {
9027     int fromX, fromY, toX, toY;
9028     char promoChar;
9029     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9030         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9031           case CMAIL_MOVE:
9032           case CMAIL_DRAW:
9033             if (appData.debugMode)
9034               fprintf(debugFP, "Restoring %s for game %d\n",
9035                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9036     
9037             thinkOutput[0] = NULLCHAR;
9038             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9039             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9040             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9041             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9042             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9043             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9044             MakeMove(fromX, fromY, toX, toY, promoChar);
9045             ShowMove(fromX, fromY, toX, toY);
9046               
9047             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9048               case MT_NONE:
9049               case MT_CHECK:
9050                 break;
9051                 
9052               case MT_CHECKMATE:
9053               case MT_STAINMATE:
9054                 if (WhiteOnMove(currentMove)) {
9055                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9056                 } else {
9057                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9058                 }
9059                 break;
9060                 
9061               case MT_STALEMATE:
9062                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9063                 break;
9064             }
9065
9066             break;
9067             
9068           case CMAIL_RESIGN:
9069             if (WhiteOnMove(currentMove)) {
9070                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9071             } else {
9072                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9073             }
9074             break;
9075             
9076           case CMAIL_ACCEPT:
9077             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9078             break;
9079               
9080           default:
9081             break;
9082         }
9083     }
9084
9085     return;
9086 }
9087
9088 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9089 int
9090 CmailLoadGame(f, gameNumber, title, useList)
9091      FILE *f;
9092      int gameNumber;
9093      char *title;
9094      int useList;
9095 {
9096     int retVal;
9097
9098     if (gameNumber > nCmailGames) {
9099         DisplayError(_("No more games in this message"), 0);
9100         return FALSE;
9101     }
9102     if (f == lastLoadGameFP) {
9103         int offset = gameNumber - lastLoadGameNumber;
9104         if (offset == 0) {
9105             cmailMsg[0] = NULLCHAR;
9106             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9107                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9108                 nCmailMovesRegistered--;
9109             }
9110             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9111             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9112                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9113             }
9114         } else {
9115             if (! RegisterMove()) return FALSE;
9116         }
9117     }
9118
9119     retVal = LoadGame(f, gameNumber, title, useList);
9120
9121     /* Make move registered during previous look at this game, if any */
9122     MakeRegisteredMove();
9123
9124     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9125         commentList[currentMove]
9126           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9127         DisplayComment(currentMove - 1, commentList[currentMove]);
9128     }
9129
9130     return retVal;
9131 }
9132
9133 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9134 int
9135 ReloadGame(offset)
9136      int offset;
9137 {
9138     int gameNumber = lastLoadGameNumber + offset;
9139     if (lastLoadGameFP == NULL) {
9140         DisplayError(_("No game has been loaded yet"), 0);
9141         return FALSE;
9142     }
9143     if (gameNumber <= 0) {
9144         DisplayError(_("Can't back up any further"), 0);
9145         return FALSE;
9146     }
9147     if (cmailMsgLoaded) {
9148         return CmailLoadGame(lastLoadGameFP, gameNumber,
9149                              lastLoadGameTitle, lastLoadGameUseList);
9150     } else {
9151         return LoadGame(lastLoadGameFP, gameNumber,
9152                         lastLoadGameTitle, lastLoadGameUseList);
9153     }
9154 }
9155
9156
9157
9158 /* Load the nth game from open file f */
9159 int
9160 LoadGame(f, gameNumber, title, useList)
9161      FILE *f;
9162      int gameNumber;
9163      char *title;
9164      int useList;
9165 {
9166     ChessMove cm;
9167     char buf[MSG_SIZ];
9168     int gn = gameNumber;
9169     ListGame *lg = NULL;
9170     int numPGNTags = 0;
9171     int err;
9172     GameMode oldGameMode;
9173     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9174
9175     if (appData.debugMode) 
9176         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9177
9178     if (gameMode == Training )
9179         SetTrainingModeOff();
9180
9181     oldGameMode = gameMode;
9182     if (gameMode != BeginningOfGame) {
9183       Reset(FALSE, TRUE);
9184     }
9185
9186     gameFileFP = f;
9187     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9188         fclose(lastLoadGameFP);
9189     }
9190
9191     if (useList) {
9192         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9193         
9194         if (lg) {
9195             fseek(f, lg->offset, 0);
9196             GameListHighlight(gameNumber);
9197             gn = 1;
9198         }
9199         else {
9200             DisplayError(_("Game number out of range"), 0);
9201             return FALSE;
9202         }
9203     } else {
9204         GameListDestroy();
9205         if (fseek(f, 0, 0) == -1) {
9206             if (f == lastLoadGameFP ?
9207                 gameNumber == lastLoadGameNumber + 1 :
9208                 gameNumber == 1) {
9209                 gn = 1;
9210             } else {
9211                 DisplayError(_("Can't seek on game file"), 0);
9212                 return FALSE;
9213             }
9214         }
9215     }
9216     lastLoadGameFP = f;
9217     lastLoadGameNumber = gameNumber;
9218     strcpy(lastLoadGameTitle, title);
9219     lastLoadGameUseList = useList;
9220
9221     yynewfile(f);
9222
9223     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9224       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9225                 lg->gameInfo.black);
9226             DisplayTitle(buf);
9227     } else if (*title != NULLCHAR) {
9228         if (gameNumber > 1) {
9229             sprintf(buf, "%s %d", title, gameNumber);
9230             DisplayTitle(buf);
9231         } else {
9232             DisplayTitle(title);
9233         }
9234     }
9235
9236     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9237         gameMode = PlayFromGameFile;
9238         ModeHighlight();
9239     }
9240
9241     currentMove = forwardMostMove = backwardMostMove = 0;
9242     CopyBoard(boards[0], initialPosition);
9243     StopClocks();
9244
9245     /*
9246      * Skip the first gn-1 games in the file.
9247      * Also skip over anything that precedes an identifiable 
9248      * start of game marker, to avoid being confused by 
9249      * garbage at the start of the file.  Currently 
9250      * recognized start of game markers are the move number "1",
9251      * the pattern "gnuchess .* game", the pattern
9252      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9253      * A game that starts with one of the latter two patterns
9254      * will also have a move number 1, possibly
9255      * following a position diagram.
9256      * 5-4-02: Let's try being more lenient and allowing a game to
9257      * start with an unnumbered move.  Does that break anything?
9258      */
9259     cm = lastLoadGameStart = (ChessMove) 0;
9260     while (gn > 0) {
9261         yyboardindex = forwardMostMove;
9262         cm = (ChessMove) yylex();
9263         switch (cm) {
9264           case (ChessMove) 0:
9265             if (cmailMsgLoaded) {
9266                 nCmailGames = CMAIL_MAX_GAMES - gn;
9267             } else {
9268                 Reset(TRUE, TRUE);
9269                 DisplayError(_("Game not found in file"), 0);
9270             }
9271             return FALSE;
9272
9273           case GNUChessGame:
9274           case XBoardGame:
9275             gn--;
9276             lastLoadGameStart = cm;
9277             break;
9278             
9279           case MoveNumberOne:
9280             switch (lastLoadGameStart) {
9281               case GNUChessGame:
9282               case XBoardGame:
9283               case PGNTag:
9284                 break;
9285               case MoveNumberOne:
9286               case (ChessMove) 0:
9287                 gn--;           /* count this game */
9288                 lastLoadGameStart = cm;
9289                 break;
9290               default:
9291                 /* impossible */
9292                 break;
9293             }
9294             break;
9295
9296           case PGNTag:
9297             switch (lastLoadGameStart) {
9298               case GNUChessGame:
9299               case PGNTag:
9300               case MoveNumberOne:
9301               case (ChessMove) 0:
9302                 gn--;           /* count this game */
9303                 lastLoadGameStart = cm;
9304                 break;
9305               case XBoardGame:
9306                 lastLoadGameStart = cm; /* game counted already */
9307                 break;
9308               default:
9309                 /* impossible */
9310                 break;
9311             }
9312             if (gn > 0) {
9313                 do {
9314                     yyboardindex = forwardMostMove;
9315                     cm = (ChessMove) yylex();
9316                 } while (cm == PGNTag || cm == Comment);
9317             }
9318             break;
9319
9320           case WhiteWins:
9321           case BlackWins:
9322           case GameIsDrawn:
9323             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9324                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9325                     != CMAIL_OLD_RESULT) {
9326                     nCmailResults ++ ;
9327                     cmailResult[  CMAIL_MAX_GAMES
9328                                 - gn - 1] = CMAIL_OLD_RESULT;
9329                 }
9330             }
9331             break;
9332
9333           case NormalMove:
9334             /* Only a NormalMove can be at the start of a game
9335              * without a position diagram. */
9336             if (lastLoadGameStart == (ChessMove) 0) {
9337               gn--;
9338               lastLoadGameStart = MoveNumberOne;
9339             }
9340             break;
9341
9342           default:
9343             break;
9344         }
9345     }
9346     
9347     if (appData.debugMode)
9348       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9349
9350     if (cm == XBoardGame) {
9351         /* Skip any header junk before position diagram and/or move 1 */
9352         for (;;) {
9353             yyboardindex = forwardMostMove;
9354             cm = (ChessMove) yylex();
9355
9356             if (cm == (ChessMove) 0 ||
9357                 cm == GNUChessGame || cm == XBoardGame) {
9358                 /* Empty game; pretend end-of-file and handle later */
9359                 cm = (ChessMove) 0;
9360                 break;
9361             }
9362
9363             if (cm == MoveNumberOne || cm == PositionDiagram ||
9364                 cm == PGNTag || cm == Comment)
9365               break;
9366         }
9367     } else if (cm == GNUChessGame) {
9368         if (gameInfo.event != NULL) {
9369             free(gameInfo.event);
9370         }
9371         gameInfo.event = StrSave(yy_text);
9372     }   
9373
9374     startedFromSetupPosition = FALSE;
9375     while (cm == PGNTag) {
9376         if (appData.debugMode) 
9377           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9378         err = ParsePGNTag(yy_text, &gameInfo);
9379         if (!err) numPGNTags++;
9380
9381         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9382         if(gameInfo.variant != oldVariant) {
9383             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9384             InitPosition(TRUE);
9385             oldVariant = gameInfo.variant;
9386             if (appData.debugMode) 
9387               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9388         }
9389
9390
9391         if (gameInfo.fen != NULL) {
9392           Board initial_position;
9393           startedFromSetupPosition = TRUE;
9394           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9395             Reset(TRUE, TRUE);
9396             DisplayError(_("Bad FEN position in file"), 0);
9397             return FALSE;
9398           }
9399           CopyBoard(boards[0], initial_position);
9400           if (blackPlaysFirst) {
9401             currentMove = forwardMostMove = backwardMostMove = 1;
9402             CopyBoard(boards[1], initial_position);
9403             strcpy(moveList[0], "");
9404             strcpy(parseList[0], "");
9405             timeRemaining[0][1] = whiteTimeRemaining;
9406             timeRemaining[1][1] = blackTimeRemaining;
9407             if (commentList[0] != NULL) {
9408               commentList[1] = commentList[0];
9409               commentList[0] = NULL;
9410             }
9411           } else {
9412             currentMove = forwardMostMove = backwardMostMove = 0;
9413           }
9414           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9415           {   int i;
9416               initialRulePlies = FENrulePlies;
9417               for( i=0; i< nrCastlingRights; i++ )
9418                   initialRights[i] = initial_position[CASTLING][i];
9419           }
9420           yyboardindex = forwardMostMove;
9421           free(gameInfo.fen);
9422           gameInfo.fen = NULL;
9423         }
9424
9425         yyboardindex = forwardMostMove;
9426         cm = (ChessMove) yylex();
9427
9428         /* Handle comments interspersed among the tags */
9429         while (cm == Comment) {
9430             char *p;
9431             if (appData.debugMode) 
9432               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9433             p = yy_text;
9434             AppendComment(currentMove, p, FALSE);
9435             yyboardindex = forwardMostMove;
9436             cm = (ChessMove) yylex();
9437         }
9438     }
9439
9440     /* don't rely on existence of Event tag since if game was
9441      * pasted from clipboard the Event tag may not exist
9442      */
9443     if (numPGNTags > 0){
9444         char *tags;
9445         if (gameInfo.variant == VariantNormal) {
9446           gameInfo.variant = StringToVariant(gameInfo.event);
9447         }
9448         if (!matchMode) {
9449           if( appData.autoDisplayTags ) {
9450             tags = PGNTags(&gameInfo);
9451             TagsPopUp(tags, CmailMsg());
9452             free(tags);
9453           }
9454         }
9455     } else {
9456         /* Make something up, but don't display it now */
9457         SetGameInfo();
9458         TagsPopDown();
9459     }
9460
9461     if (cm == PositionDiagram) {
9462         int i, j;
9463         char *p;
9464         Board initial_position;
9465
9466         if (appData.debugMode)
9467           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9468
9469         if (!startedFromSetupPosition) {
9470             p = yy_text;
9471             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9472               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9473                 switch (*p) {
9474                   case '[':
9475                   case '-':
9476                   case ' ':
9477                   case '\t':
9478                   case '\n':
9479                   case '\r':
9480                     break;
9481                   default:
9482                     initial_position[i][j++] = CharToPiece(*p);
9483                     break;
9484                 }
9485             while (*p == ' ' || *p == '\t' ||
9486                    *p == '\n' || *p == '\r') p++;
9487         
9488             if (strncmp(p, "black", strlen("black"))==0)
9489               blackPlaysFirst = TRUE;
9490             else
9491               blackPlaysFirst = FALSE;
9492             startedFromSetupPosition = TRUE;
9493         
9494             CopyBoard(boards[0], initial_position);
9495             if (blackPlaysFirst) {
9496                 currentMove = forwardMostMove = backwardMostMove = 1;
9497                 CopyBoard(boards[1], initial_position);
9498                 strcpy(moveList[0], "");
9499                 strcpy(parseList[0], "");
9500                 timeRemaining[0][1] = whiteTimeRemaining;
9501                 timeRemaining[1][1] = blackTimeRemaining;
9502                 if (commentList[0] != NULL) {
9503                     commentList[1] = commentList[0];
9504                     commentList[0] = NULL;
9505                 }
9506             } else {
9507                 currentMove = forwardMostMove = backwardMostMove = 0;
9508             }
9509         }
9510         yyboardindex = forwardMostMove;
9511         cm = (ChessMove) yylex();
9512     }
9513
9514     if (first.pr == NoProc) {
9515         StartChessProgram(&first);
9516     }
9517     InitChessProgram(&first, FALSE);
9518     SendToProgram("force\n", &first);
9519     if (startedFromSetupPosition) {
9520         SendBoard(&first, forwardMostMove);
9521     if (appData.debugMode) {
9522         fprintf(debugFP, "Load Game\n");
9523     }
9524         DisplayBothClocks();
9525     }      
9526
9527     /* [HGM] server: flag to write setup moves in broadcast file as one */
9528     loadFlag = appData.suppressLoadMoves;
9529
9530     while (cm == Comment) {
9531         char *p;
9532         if (appData.debugMode) 
9533           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9534         p = yy_text;
9535         AppendComment(currentMove, p, FALSE);
9536         yyboardindex = forwardMostMove;
9537         cm = (ChessMove) yylex();
9538     }
9539
9540     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9541         cm == WhiteWins || cm == BlackWins ||
9542         cm == GameIsDrawn || cm == GameUnfinished) {
9543         DisplayMessage("", _("No moves in game"));
9544         if (cmailMsgLoaded) {
9545             if (appData.debugMode)
9546               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9547             ClearHighlights();
9548             flipView = FALSE;
9549         }
9550         DrawPosition(FALSE, boards[currentMove]);
9551         DisplayBothClocks();
9552         gameMode = EditGame;
9553         ModeHighlight();
9554         gameFileFP = NULL;
9555         cmailOldMove = 0;
9556         return TRUE;
9557     }
9558
9559     // [HGM] PV info: routine tests if comment empty
9560     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9561         DisplayComment(currentMove - 1, commentList[currentMove]);
9562     }
9563     if (!matchMode && appData.timeDelay != 0) 
9564       DrawPosition(FALSE, boards[currentMove]);
9565
9566     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9567       programStats.ok_to_send = 1;
9568     }
9569
9570     /* if the first token after the PGN tags is a move
9571      * and not move number 1, retrieve it from the parser 
9572      */
9573     if (cm != MoveNumberOne)
9574         LoadGameOneMove(cm);
9575
9576     /* load the remaining moves from the file */
9577     while (LoadGameOneMove((ChessMove)0)) {
9578       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9579       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9580     }
9581
9582     /* rewind to the start of the game */
9583     currentMove = backwardMostMove;
9584
9585     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9586
9587     if (oldGameMode == AnalyzeFile ||
9588         oldGameMode == AnalyzeMode) {
9589       AnalyzeFileEvent();
9590     }
9591
9592     if (matchMode || appData.timeDelay == 0) {
9593       ToEndEvent();
9594       gameMode = EditGame;
9595       ModeHighlight();
9596     } else if (appData.timeDelay > 0) {
9597       AutoPlayGameLoop();
9598     }
9599
9600     if (appData.debugMode) 
9601         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9602
9603     loadFlag = 0; /* [HGM] true game starts */
9604     return TRUE;
9605 }
9606
9607 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9608 int
9609 ReloadPosition(offset)
9610      int offset;
9611 {
9612     int positionNumber = lastLoadPositionNumber + offset;
9613     if (lastLoadPositionFP == NULL) {
9614         DisplayError(_("No position has been loaded yet"), 0);
9615         return FALSE;
9616     }
9617     if (positionNumber <= 0) {
9618         DisplayError(_("Can't back up any further"), 0);
9619         return FALSE;
9620     }
9621     return LoadPosition(lastLoadPositionFP, positionNumber,
9622                         lastLoadPositionTitle);
9623 }
9624
9625 /* Load the nth position from the given file */
9626 int
9627 LoadPositionFromFile(filename, n, title)
9628      char *filename;
9629      int n;
9630      char *title;
9631 {
9632     FILE *f;
9633     char buf[MSG_SIZ];
9634
9635     if (strcmp(filename, "-") == 0) {
9636         return LoadPosition(stdin, n, "stdin");
9637     } else {
9638         f = fopen(filename, "rb");
9639         if (f == NULL) {
9640             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9641             DisplayError(buf, errno);
9642             return FALSE;
9643         } else {
9644             return LoadPosition(f, n, title);
9645         }
9646     }
9647 }
9648
9649 /* Load the nth position from the given open file, and close it */
9650 int
9651 LoadPosition(f, positionNumber, title)
9652      FILE *f;
9653      int positionNumber;
9654      char *title;
9655 {
9656     char *p, line[MSG_SIZ];
9657     Board initial_position;
9658     int i, j, fenMode, pn;
9659     
9660     if (gameMode == Training )
9661         SetTrainingModeOff();
9662
9663     if (gameMode != BeginningOfGame) {
9664         Reset(FALSE, TRUE);
9665     }
9666     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9667         fclose(lastLoadPositionFP);
9668     }
9669     if (positionNumber == 0) positionNumber = 1;
9670     lastLoadPositionFP = f;
9671     lastLoadPositionNumber = positionNumber;
9672     strcpy(lastLoadPositionTitle, title);
9673     if (first.pr == NoProc) {
9674       StartChessProgram(&first);
9675       InitChessProgram(&first, FALSE);
9676     }    
9677     pn = positionNumber;
9678     if (positionNumber < 0) {
9679         /* Negative position number means to seek to that byte offset */
9680         if (fseek(f, -positionNumber, 0) == -1) {
9681             DisplayError(_("Can't seek on position file"), 0);
9682             return FALSE;
9683         };
9684         pn = 1;
9685     } else {
9686         if (fseek(f, 0, 0) == -1) {
9687             if (f == lastLoadPositionFP ?
9688                 positionNumber == lastLoadPositionNumber + 1 :
9689                 positionNumber == 1) {
9690                 pn = 1;
9691             } else {
9692                 DisplayError(_("Can't seek on position file"), 0);
9693                 return FALSE;
9694             }
9695         }
9696     }
9697     /* See if this file is FEN or old-style xboard */
9698     if (fgets(line, MSG_SIZ, f) == NULL) {
9699         DisplayError(_("Position not found in file"), 0);
9700         return FALSE;
9701     }
9702     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9703     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9704
9705     if (pn >= 2) {
9706         if (fenMode || line[0] == '#') pn--;
9707         while (pn > 0) {
9708             /* skip positions before number pn */
9709             if (fgets(line, MSG_SIZ, f) == NULL) {
9710                 Reset(TRUE, TRUE);
9711                 DisplayError(_("Position not found in file"), 0);
9712                 return FALSE;
9713             }
9714             if (fenMode || line[0] == '#') pn--;
9715         }
9716     }
9717
9718     if (fenMode) {
9719         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9720             DisplayError(_("Bad FEN position in file"), 0);
9721             return FALSE;
9722         }
9723     } else {
9724         (void) fgets(line, MSG_SIZ, f);
9725         (void) fgets(line, MSG_SIZ, f);
9726     
9727         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9728             (void) fgets(line, MSG_SIZ, f);
9729             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9730                 if (*p == ' ')
9731                   continue;
9732                 initial_position[i][j++] = CharToPiece(*p);
9733             }
9734         }
9735     
9736         blackPlaysFirst = FALSE;
9737         if (!feof(f)) {
9738             (void) fgets(line, MSG_SIZ, f);
9739             if (strncmp(line, "black", strlen("black"))==0)
9740               blackPlaysFirst = TRUE;
9741         }
9742     }
9743     startedFromSetupPosition = TRUE;
9744     
9745     SendToProgram("force\n", &first);
9746     CopyBoard(boards[0], initial_position);
9747     if (blackPlaysFirst) {
9748         currentMove = forwardMostMove = backwardMostMove = 1;
9749         strcpy(moveList[0], "");
9750         strcpy(parseList[0], "");
9751         CopyBoard(boards[1], initial_position);
9752         DisplayMessage("", _("Black to play"));
9753     } else {
9754         currentMove = forwardMostMove = backwardMostMove = 0;
9755         DisplayMessage("", _("White to play"));
9756     }
9757     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9758     SendBoard(&first, forwardMostMove);
9759     if (appData.debugMode) {
9760 int i, j;
9761   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9762   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9763         fprintf(debugFP, "Load Position\n");
9764     }
9765
9766     if (positionNumber > 1) {
9767         sprintf(line, "%s %d", title, positionNumber);
9768         DisplayTitle(line);
9769     } else {
9770         DisplayTitle(title);
9771     }
9772     gameMode = EditGame;
9773     ModeHighlight();
9774     ResetClocks();
9775     timeRemaining[0][1] = whiteTimeRemaining;
9776     timeRemaining[1][1] = blackTimeRemaining;
9777     DrawPosition(FALSE, boards[currentMove]);
9778    
9779     return TRUE;
9780 }
9781
9782
9783 void
9784 CopyPlayerNameIntoFileName(dest, src)
9785      char **dest, *src;
9786 {
9787     while (*src != NULLCHAR && *src != ',') {
9788         if (*src == ' ') {
9789             *(*dest)++ = '_';
9790             src++;
9791         } else {
9792             *(*dest)++ = *src++;
9793         }
9794     }
9795 }
9796
9797 char *DefaultFileName(ext)
9798      char *ext;
9799 {
9800     static char def[MSG_SIZ];
9801     char *p;
9802
9803     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9804         p = def;
9805         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9806         *p++ = '-';
9807         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9808         *p++ = '.';
9809         strcpy(p, ext);
9810     } else {
9811         def[0] = NULLCHAR;
9812     }
9813     return def;
9814 }
9815
9816 /* Save the current game to the given file */
9817 int
9818 SaveGameToFile(filename, append)
9819      char *filename;
9820      int append;
9821 {
9822     FILE *f;
9823     char buf[MSG_SIZ];
9824
9825     if (strcmp(filename, "-") == 0) {
9826         return SaveGame(stdout, 0, NULL);
9827     } else {
9828         f = fopen(filename, append ? "a" : "w");
9829         if (f == NULL) {
9830             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9831             DisplayError(buf, errno);
9832             return FALSE;
9833         } else {
9834             return SaveGame(f, 0, NULL);
9835         }
9836     }
9837 }
9838
9839 char *
9840 SavePart(str)
9841      char *str;
9842 {
9843     static char buf[MSG_SIZ];
9844     char *p;
9845     
9846     p = strchr(str, ' ');
9847     if (p == NULL) return str;
9848     strncpy(buf, str, p - str);
9849     buf[p - str] = NULLCHAR;
9850     return buf;
9851 }
9852
9853 #define PGN_MAX_LINE 75
9854
9855 #define PGN_SIDE_WHITE  0
9856 #define PGN_SIDE_BLACK  1
9857
9858 /* [AS] */
9859 static int FindFirstMoveOutOfBook( int side )
9860 {
9861     int result = -1;
9862
9863     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9864         int index = backwardMostMove;
9865         int has_book_hit = 0;
9866
9867         if( (index % 2) != side ) {
9868             index++;
9869         }
9870
9871         while( index < forwardMostMove ) {
9872             /* Check to see if engine is in book */
9873             int depth = pvInfoList[index].depth;
9874             int score = pvInfoList[index].score;
9875             int in_book = 0;
9876
9877             if( depth <= 2 ) {
9878                 in_book = 1;
9879             }
9880             else if( score == 0 && depth == 63 ) {
9881                 in_book = 1; /* Zappa */
9882             }
9883             else if( score == 2 && depth == 99 ) {
9884                 in_book = 1; /* Abrok */
9885             }
9886
9887             has_book_hit += in_book;
9888
9889             if( ! in_book ) {
9890                 result = index;
9891
9892                 break;
9893             }
9894
9895             index += 2;
9896         }
9897     }
9898
9899     return result;
9900 }
9901
9902 /* [AS] */
9903 void GetOutOfBookInfo( char * buf )
9904 {
9905     int oob[2];
9906     int i;
9907     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9908
9909     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9910     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9911
9912     *buf = '\0';
9913
9914     if( oob[0] >= 0 || oob[1] >= 0 ) {
9915         for( i=0; i<2; i++ ) {
9916             int idx = oob[i];
9917
9918             if( idx >= 0 ) {
9919                 if( i > 0 && oob[0] >= 0 ) {
9920                     strcat( buf, "   " );
9921                 }
9922
9923                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9924                 sprintf( buf+strlen(buf), "%s%.2f", 
9925                     pvInfoList[idx].score >= 0 ? "+" : "",
9926                     pvInfoList[idx].score / 100.0 );
9927             }
9928         }
9929     }
9930 }
9931
9932 /* Save game in PGN style and close the file */
9933 int
9934 SaveGamePGN(f)
9935      FILE *f;
9936 {
9937     int i, offset, linelen, newblock;
9938     time_t tm;
9939 //    char *movetext;
9940     char numtext[32];
9941     int movelen, numlen, blank;
9942     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9943
9944     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9945     
9946     tm = time((time_t *) NULL);
9947     
9948     PrintPGNTags(f, &gameInfo);
9949     
9950     if (backwardMostMove > 0 || startedFromSetupPosition) {
9951         char *fen = PositionToFEN(backwardMostMove, NULL);
9952         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9953         fprintf(f, "\n{--------------\n");
9954         PrintPosition(f, backwardMostMove);
9955         fprintf(f, "--------------}\n");
9956         free(fen);
9957     }
9958     else {
9959         /* [AS] Out of book annotation */
9960         if( appData.saveOutOfBookInfo ) {
9961             char buf[64];
9962
9963             GetOutOfBookInfo( buf );
9964
9965             if( buf[0] != '\0' ) {
9966                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9967             }
9968         }
9969
9970         fprintf(f, "\n");
9971     }
9972
9973     i = backwardMostMove;
9974     linelen = 0;
9975     newblock = TRUE;
9976
9977     while (i < forwardMostMove) {
9978         /* Print comments preceding this move */
9979         if (commentList[i] != NULL) {
9980             if (linelen > 0) fprintf(f, "\n");
9981             fprintf(f, "%s", commentList[i]);
9982             linelen = 0;
9983             newblock = TRUE;
9984         }
9985
9986         /* Format move number */
9987         if ((i % 2) == 0) {
9988             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9989         } else {
9990             if (newblock) {
9991                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9992             } else {
9993                 numtext[0] = NULLCHAR;
9994             }
9995         }
9996         numlen = strlen(numtext);
9997         newblock = FALSE;
9998
9999         /* Print move number */
10000         blank = linelen > 0 && numlen > 0;
10001         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10002             fprintf(f, "\n");
10003             linelen = 0;
10004             blank = 0;
10005         }
10006         if (blank) {
10007             fprintf(f, " ");
10008             linelen++;
10009         }
10010         fprintf(f, "%s", numtext);
10011         linelen += numlen;
10012
10013         /* Get move */
10014         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10015         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10016
10017         /* Print move */
10018         blank = linelen > 0 && movelen > 0;
10019         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10020             fprintf(f, "\n");
10021             linelen = 0;
10022             blank = 0;
10023         }
10024         if (blank) {
10025             fprintf(f, " ");
10026             linelen++;
10027         }
10028         fprintf(f, "%s", move_buffer);
10029         linelen += movelen;
10030
10031         /* [AS] Add PV info if present */
10032         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10033             /* [HGM] add time */
10034             char buf[MSG_SIZ]; int seconds;
10035
10036             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10037
10038             if( seconds <= 0) buf[0] = 0; else
10039             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10040                 seconds = (seconds + 4)/10; // round to full seconds
10041                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10042                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10043             }
10044
10045             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10046                 pvInfoList[i].score >= 0 ? "+" : "",
10047                 pvInfoList[i].score / 100.0,
10048                 pvInfoList[i].depth,
10049                 buf );
10050
10051             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10052
10053             /* Print score/depth */
10054             blank = linelen > 0 && movelen > 0;
10055             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10056                 fprintf(f, "\n");
10057                 linelen = 0;
10058                 blank = 0;
10059             }
10060             if (blank) {
10061                 fprintf(f, " ");
10062                 linelen++;
10063             }
10064             fprintf(f, "%s", move_buffer);
10065             linelen += movelen;
10066         }
10067
10068         i++;
10069     }
10070     
10071     /* Start a new line */
10072     if (linelen > 0) fprintf(f, "\n");
10073
10074     /* Print comments after last move */
10075     if (commentList[i] != NULL) {
10076         fprintf(f, "%s\n", commentList[i]);
10077     }
10078
10079     /* Print result */
10080     if (gameInfo.resultDetails != NULL &&
10081         gameInfo.resultDetails[0] != NULLCHAR) {
10082         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10083                 PGNResult(gameInfo.result));
10084     } else {
10085         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10086     }
10087
10088     fclose(f);
10089     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10090     return TRUE;
10091 }
10092
10093 /* Save game in old style and close the file */
10094 int
10095 SaveGameOldStyle(f)
10096      FILE *f;
10097 {
10098     int i, offset;
10099     time_t tm;
10100     
10101     tm = time((time_t *) NULL);
10102     
10103     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10104     PrintOpponents(f);
10105     
10106     if (backwardMostMove > 0 || startedFromSetupPosition) {
10107         fprintf(f, "\n[--------------\n");
10108         PrintPosition(f, backwardMostMove);
10109         fprintf(f, "--------------]\n");
10110     } else {
10111         fprintf(f, "\n");
10112     }
10113
10114     i = backwardMostMove;
10115     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10116
10117     while (i < forwardMostMove) {
10118         if (commentList[i] != NULL) {
10119             fprintf(f, "[%s]\n", commentList[i]);
10120         }
10121
10122         if ((i % 2) == 1) {
10123             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10124             i++;
10125         } else {
10126             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10127             i++;
10128             if (commentList[i] != NULL) {
10129                 fprintf(f, "\n");
10130                 continue;
10131             }
10132             if (i >= forwardMostMove) {
10133                 fprintf(f, "\n");
10134                 break;
10135             }
10136             fprintf(f, "%s\n", parseList[i]);
10137             i++;
10138         }
10139     }
10140     
10141     if (commentList[i] != NULL) {
10142         fprintf(f, "[%s]\n", commentList[i]);
10143     }
10144
10145     /* This isn't really the old style, but it's close enough */
10146     if (gameInfo.resultDetails != NULL &&
10147         gameInfo.resultDetails[0] != NULLCHAR) {
10148         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10149                 gameInfo.resultDetails);
10150     } else {
10151         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10152     }
10153
10154     fclose(f);
10155     return TRUE;
10156 }
10157
10158 /* Save the current game to open file f and close the file */
10159 int
10160 SaveGame(f, dummy, dummy2)
10161      FILE *f;
10162      int dummy;
10163      char *dummy2;
10164 {
10165     if (gameMode == EditPosition) EditPositionDone(TRUE);
10166     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10167     if (appData.oldSaveStyle)
10168       return SaveGameOldStyle(f);
10169     else
10170       return SaveGamePGN(f);
10171 }
10172
10173 /* Save the current position to the given file */
10174 int
10175 SavePositionToFile(filename)
10176      char *filename;
10177 {
10178     FILE *f;
10179     char buf[MSG_SIZ];
10180
10181     if (strcmp(filename, "-") == 0) {
10182         return SavePosition(stdout, 0, NULL);
10183     } else {
10184         f = fopen(filename, "a");
10185         if (f == NULL) {
10186             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10187             DisplayError(buf, errno);
10188             return FALSE;
10189         } else {
10190             SavePosition(f, 0, NULL);
10191             return TRUE;
10192         }
10193     }
10194 }
10195
10196 /* Save the current position to the given open file and close the file */
10197 int
10198 SavePosition(f, dummy, dummy2)
10199      FILE *f;
10200      int dummy;
10201      char *dummy2;
10202 {
10203     time_t tm;
10204     char *fen;
10205     
10206     if (gameMode == EditPosition) EditPositionDone(TRUE);
10207     if (appData.oldSaveStyle) {
10208         tm = time((time_t *) NULL);
10209     
10210         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10211         PrintOpponents(f);
10212         fprintf(f, "[--------------\n");
10213         PrintPosition(f, currentMove);
10214         fprintf(f, "--------------]\n");
10215     } else {
10216         fen = PositionToFEN(currentMove, NULL);
10217         fprintf(f, "%s\n", fen);
10218         free(fen);
10219     }
10220     fclose(f);
10221     return TRUE;
10222 }
10223
10224 void
10225 ReloadCmailMsgEvent(unregister)
10226      int unregister;
10227 {
10228 #if !WIN32
10229     static char *inFilename = NULL;
10230     static char *outFilename;
10231     int i;
10232     struct stat inbuf, outbuf;
10233     int status;
10234     
10235     /* Any registered moves are unregistered if unregister is set, */
10236     /* i.e. invoked by the signal handler */
10237     if (unregister) {
10238         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10239             cmailMoveRegistered[i] = FALSE;
10240             if (cmailCommentList[i] != NULL) {
10241                 free(cmailCommentList[i]);
10242                 cmailCommentList[i] = NULL;
10243             }
10244         }
10245         nCmailMovesRegistered = 0;
10246     }
10247
10248     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10249         cmailResult[i] = CMAIL_NOT_RESULT;
10250     }
10251     nCmailResults = 0;
10252
10253     if (inFilename == NULL) {
10254         /* Because the filenames are static they only get malloced once  */
10255         /* and they never get freed                                      */
10256         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10257         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10258
10259         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10260         sprintf(outFilename, "%s.out", appData.cmailGameName);
10261     }
10262     
10263     status = stat(outFilename, &outbuf);
10264     if (status < 0) {
10265         cmailMailedMove = FALSE;
10266     } else {
10267         status = stat(inFilename, &inbuf);
10268         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10269     }
10270     
10271     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10272        counts the games, notes how each one terminated, etc.
10273        
10274        It would be nice to remove this kludge and instead gather all
10275        the information while building the game list.  (And to keep it
10276        in the game list nodes instead of having a bunch of fixed-size
10277        parallel arrays.)  Note this will require getting each game's
10278        termination from the PGN tags, as the game list builder does
10279        not process the game moves.  --mann
10280        */
10281     cmailMsgLoaded = TRUE;
10282     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10283     
10284     /* Load first game in the file or popup game menu */
10285     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10286
10287 #endif /* !WIN32 */
10288     return;
10289 }
10290
10291 int
10292 RegisterMove()
10293 {
10294     FILE *f;
10295     char string[MSG_SIZ];
10296
10297     if (   cmailMailedMove
10298         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10299         return TRUE;            /* Allow free viewing  */
10300     }
10301
10302     /* Unregister move to ensure that we don't leave RegisterMove        */
10303     /* with the move registered when the conditions for registering no   */
10304     /* longer hold                                                       */
10305     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10306         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10307         nCmailMovesRegistered --;
10308
10309         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10310           {
10311               free(cmailCommentList[lastLoadGameNumber - 1]);
10312               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10313           }
10314     }
10315
10316     if (cmailOldMove == -1) {
10317         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10318         return FALSE;
10319     }
10320
10321     if (currentMove > cmailOldMove + 1) {
10322         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10323         return FALSE;
10324     }
10325
10326     if (currentMove < cmailOldMove) {
10327         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10328         return FALSE;
10329     }
10330
10331     if (forwardMostMove > currentMove) {
10332         /* Silently truncate extra moves */
10333         TruncateGame();
10334     }
10335
10336     if (   (currentMove == cmailOldMove + 1)
10337         || (   (currentMove == cmailOldMove)
10338             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10339                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10340         if (gameInfo.result != GameUnfinished) {
10341             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10342         }
10343
10344         if (commentList[currentMove] != NULL) {
10345             cmailCommentList[lastLoadGameNumber - 1]
10346               = StrSave(commentList[currentMove]);
10347         }
10348         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10349
10350         if (appData.debugMode)
10351           fprintf(debugFP, "Saving %s for game %d\n",
10352                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10353
10354         sprintf(string,
10355                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10356         
10357         f = fopen(string, "w");
10358         if (appData.oldSaveStyle) {
10359             SaveGameOldStyle(f); /* also closes the file */
10360             
10361             sprintf(string, "%s.pos.out", appData.cmailGameName);
10362             f = fopen(string, "w");
10363             SavePosition(f, 0, NULL); /* also closes the file */
10364         } else {
10365             fprintf(f, "{--------------\n");
10366             PrintPosition(f, currentMove);
10367             fprintf(f, "--------------}\n\n");
10368             
10369             SaveGame(f, 0, NULL); /* also closes the file*/
10370         }
10371         
10372         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10373         nCmailMovesRegistered ++;
10374     } else if (nCmailGames == 1) {
10375         DisplayError(_("You have not made a move yet"), 0);
10376         return FALSE;
10377     }
10378
10379     return TRUE;
10380 }
10381
10382 void
10383 MailMoveEvent()
10384 {
10385 #if !WIN32
10386     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10387     FILE *commandOutput;
10388     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10389     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10390     int nBuffers;
10391     int i;
10392     int archived;
10393     char *arcDir;
10394
10395     if (! cmailMsgLoaded) {
10396         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10397         return;
10398     }
10399
10400     if (nCmailGames == nCmailResults) {
10401         DisplayError(_("No unfinished games"), 0);
10402         return;
10403     }
10404
10405 #if CMAIL_PROHIBIT_REMAIL
10406     if (cmailMailedMove) {
10407         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);
10408         DisplayError(msg, 0);
10409         return;
10410     }
10411 #endif
10412
10413     if (! (cmailMailedMove || RegisterMove())) return;
10414     
10415     if (   cmailMailedMove
10416         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10417         sprintf(string, partCommandString,
10418                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10419         commandOutput = popen(string, "r");
10420
10421         if (commandOutput == NULL) {
10422             DisplayError(_("Failed to invoke cmail"), 0);
10423         } else {
10424             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10425                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10426             }
10427             if (nBuffers > 1) {
10428                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10429                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10430                 nBytes = MSG_SIZ - 1;
10431             } else {
10432                 (void) memcpy(msg, buffer, nBytes);
10433             }
10434             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10435
10436             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10437                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10438
10439                 archived = TRUE;
10440                 for (i = 0; i < nCmailGames; i ++) {
10441                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10442                         archived = FALSE;
10443                     }
10444                 }
10445                 if (   archived
10446                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10447                         != NULL)) {
10448                     sprintf(buffer, "%s/%s.%s.archive",
10449                             arcDir,
10450                             appData.cmailGameName,
10451                             gameInfo.date);
10452                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10453                     cmailMsgLoaded = FALSE;
10454                 }
10455             }
10456
10457             DisplayInformation(msg);
10458             pclose(commandOutput);
10459         }
10460     } else {
10461         if ((*cmailMsg) != '\0') {
10462             DisplayInformation(cmailMsg);
10463         }
10464     }
10465
10466     return;
10467 #endif /* !WIN32 */
10468 }
10469
10470 char *
10471 CmailMsg()
10472 {
10473 #if WIN32
10474     return NULL;
10475 #else
10476     int  prependComma = 0;
10477     char number[5];
10478     char string[MSG_SIZ];       /* Space for game-list */
10479     int  i;
10480     
10481     if (!cmailMsgLoaded) return "";
10482
10483     if (cmailMailedMove) {
10484         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10485     } else {
10486         /* Create a list of games left */
10487         sprintf(string, "[");
10488         for (i = 0; i < nCmailGames; i ++) {
10489             if (! (   cmailMoveRegistered[i]
10490                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10491                 if (prependComma) {
10492                     sprintf(number, ",%d", i + 1);
10493                 } else {
10494                     sprintf(number, "%d", i + 1);
10495                     prependComma = 1;
10496                 }
10497                 
10498                 strcat(string, number);
10499             }
10500         }
10501         strcat(string, "]");
10502
10503         if (nCmailMovesRegistered + nCmailResults == 0) {
10504             switch (nCmailGames) {
10505               case 1:
10506                 sprintf(cmailMsg,
10507                         _("Still need to make move for game\n"));
10508                 break;
10509                 
10510               case 2:
10511                 sprintf(cmailMsg,
10512                         _("Still need to make moves for both games\n"));
10513                 break;
10514                 
10515               default:
10516                 sprintf(cmailMsg,
10517                         _("Still need to make moves for all %d games\n"),
10518                         nCmailGames);
10519                 break;
10520             }
10521         } else {
10522             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10523               case 1:
10524                 sprintf(cmailMsg,
10525                         _("Still need to make a move for game %s\n"),
10526                         string);
10527                 break;
10528                 
10529               case 0:
10530                 if (nCmailResults == nCmailGames) {
10531                     sprintf(cmailMsg, _("No unfinished games\n"));
10532                 } else {
10533                     sprintf(cmailMsg, _("Ready to send mail\n"));
10534                 }
10535                 break;
10536                 
10537               default:
10538                 sprintf(cmailMsg,
10539                         _("Still need to make moves for games %s\n"),
10540                         string);
10541             }
10542         }
10543     }
10544     return cmailMsg;
10545 #endif /* WIN32 */
10546 }
10547
10548 void
10549 ResetGameEvent()
10550 {
10551     if (gameMode == Training)
10552       SetTrainingModeOff();
10553
10554     Reset(TRUE, TRUE);
10555     cmailMsgLoaded = FALSE;
10556     if (appData.icsActive) {
10557       SendToICS(ics_prefix);
10558       SendToICS("refresh\n");
10559     }
10560 }
10561
10562 void
10563 ExitEvent(status)
10564      int status;
10565 {
10566     exiting++;
10567     if (exiting > 2) {
10568       /* Give up on clean exit */
10569       exit(status);
10570     }
10571     if (exiting > 1) {
10572       /* Keep trying for clean exit */
10573       return;
10574     }
10575
10576     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10577
10578     if (telnetISR != NULL) {
10579       RemoveInputSource(telnetISR);
10580     }
10581     if (icsPR != NoProc) {
10582       DestroyChildProcess(icsPR, TRUE);
10583     }
10584
10585     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10586     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10587
10588     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10589     /* make sure this other one finishes before killing it!                  */
10590     if(endingGame) { int count = 0;
10591         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10592         while(endingGame && count++ < 10) DoSleep(1);
10593         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10594     }
10595
10596     /* Kill off chess programs */
10597     if (first.pr != NoProc) {
10598         ExitAnalyzeMode();
10599         
10600         DoSleep( appData.delayBeforeQuit );
10601         SendToProgram("quit\n", &first);
10602         DoSleep( appData.delayAfterQuit );
10603         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10604     }
10605     if (second.pr != NoProc) {
10606         DoSleep( appData.delayBeforeQuit );
10607         SendToProgram("quit\n", &second);
10608         DoSleep( appData.delayAfterQuit );
10609         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10610     }
10611     if (first.isr != NULL) {
10612         RemoveInputSource(first.isr);
10613     }
10614     if (second.isr != NULL) {
10615         RemoveInputSource(second.isr);
10616     }
10617
10618     ShutDownFrontEnd();
10619     exit(status);
10620 }
10621
10622 void
10623 PauseEvent()
10624 {
10625     if (appData.debugMode)
10626         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10627     if (pausing) {
10628         pausing = FALSE;
10629         ModeHighlight();
10630         if (gameMode == MachinePlaysWhite ||
10631             gameMode == MachinePlaysBlack) {
10632             StartClocks();
10633         } else {
10634             DisplayBothClocks();
10635         }
10636         if (gameMode == PlayFromGameFile) {
10637             if (appData.timeDelay >= 0) 
10638                 AutoPlayGameLoop();
10639         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10640             Reset(FALSE, TRUE);
10641             SendToICS(ics_prefix);
10642             SendToICS("refresh\n");
10643         } else if (currentMove < forwardMostMove) {
10644             ForwardInner(forwardMostMove);
10645         }
10646         pauseExamInvalid = FALSE;
10647     } else {
10648         switch (gameMode) {
10649           default:
10650             return;
10651           case IcsExamining:
10652             pauseExamForwardMostMove = forwardMostMove;
10653             pauseExamInvalid = FALSE;
10654             /* fall through */
10655           case IcsObserving:
10656           case IcsPlayingWhite:
10657           case IcsPlayingBlack:
10658             pausing = TRUE;
10659             ModeHighlight();
10660             return;
10661           case PlayFromGameFile:
10662             (void) StopLoadGameTimer();
10663             pausing = TRUE;
10664             ModeHighlight();
10665             break;
10666           case BeginningOfGame:
10667             if (appData.icsActive) return;
10668             /* else fall through */
10669           case MachinePlaysWhite:
10670           case MachinePlaysBlack:
10671           case TwoMachinesPlay:
10672             if (forwardMostMove == 0)
10673               return;           /* don't pause if no one has moved */
10674             if ((gameMode == MachinePlaysWhite &&
10675                  !WhiteOnMove(forwardMostMove)) ||
10676                 (gameMode == MachinePlaysBlack &&
10677                  WhiteOnMove(forwardMostMove))) {
10678                 StopClocks();
10679             }
10680             pausing = TRUE;
10681             ModeHighlight();
10682             break;
10683         }
10684     }
10685 }
10686
10687 void
10688 EditCommentEvent()
10689 {
10690     char title[MSG_SIZ];
10691
10692     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10693         strcpy(title, _("Edit comment"));
10694     } else {
10695         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10696                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10697                 parseList[currentMove - 1]);
10698     }
10699
10700     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10701 }
10702
10703
10704 void
10705 EditTagsEvent()
10706 {
10707     char *tags = PGNTags(&gameInfo);
10708     EditTagsPopUp(tags);
10709     free(tags);
10710 }
10711
10712 void
10713 AnalyzeModeEvent()
10714 {
10715     if (appData.noChessProgram || gameMode == AnalyzeMode)
10716       return;
10717
10718     if (gameMode != AnalyzeFile) {
10719         if (!appData.icsEngineAnalyze) {
10720                EditGameEvent();
10721                if (gameMode != EditGame) return;
10722         }
10723         ResurrectChessProgram();
10724         SendToProgram("analyze\n", &first);
10725         first.analyzing = TRUE;
10726         /*first.maybeThinking = TRUE;*/
10727         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10728         EngineOutputPopUp();
10729     }
10730     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10731     pausing = FALSE;
10732     ModeHighlight();
10733     SetGameInfo();
10734
10735     StartAnalysisClock();
10736     GetTimeMark(&lastNodeCountTime);
10737     lastNodeCount = 0;
10738 }
10739
10740 void
10741 AnalyzeFileEvent()
10742 {
10743     if (appData.noChessProgram || gameMode == AnalyzeFile)
10744       return;
10745
10746     if (gameMode != AnalyzeMode) {
10747         EditGameEvent();
10748         if (gameMode != EditGame) return;
10749         ResurrectChessProgram();
10750         SendToProgram("analyze\n", &first);
10751         first.analyzing = TRUE;
10752         /*first.maybeThinking = TRUE;*/
10753         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10754         EngineOutputPopUp();
10755     }
10756     gameMode = AnalyzeFile;
10757     pausing = FALSE;
10758     ModeHighlight();
10759     SetGameInfo();
10760
10761     StartAnalysisClock();
10762     GetTimeMark(&lastNodeCountTime);
10763     lastNodeCount = 0;
10764 }
10765
10766 void
10767 MachineWhiteEvent()
10768 {
10769     char buf[MSG_SIZ];
10770     char *bookHit = NULL;
10771
10772     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10773       return;
10774
10775
10776     if (gameMode == PlayFromGameFile || 
10777         gameMode == TwoMachinesPlay  || 
10778         gameMode == Training         || 
10779         gameMode == AnalyzeMode      || 
10780         gameMode == EndOfGame)
10781         EditGameEvent();
10782
10783     if (gameMode == EditPosition) 
10784         EditPositionDone(TRUE);
10785
10786     if (!WhiteOnMove(currentMove)) {
10787         DisplayError(_("It is not White's turn"), 0);
10788         return;
10789     }
10790   
10791     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10792       ExitAnalyzeMode();
10793
10794     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10795         gameMode == AnalyzeFile)
10796         TruncateGame();
10797
10798     ResurrectChessProgram();    /* in case it isn't running */
10799     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10800         gameMode = MachinePlaysWhite;
10801         ResetClocks();
10802     } else
10803     gameMode = MachinePlaysWhite;
10804     pausing = FALSE;
10805     ModeHighlight();
10806     SetGameInfo();
10807     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10808     DisplayTitle(buf);
10809     if (first.sendName) {
10810       sprintf(buf, "name %s\n", gameInfo.black);
10811       SendToProgram(buf, &first);
10812     }
10813     if (first.sendTime) {
10814       if (first.useColors) {
10815         SendToProgram("black\n", &first); /*gnu kludge*/
10816       }
10817       SendTimeRemaining(&first, TRUE);
10818     }
10819     if (first.useColors) {
10820       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10821     }
10822     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10823     SetMachineThinkingEnables();
10824     first.maybeThinking = TRUE;
10825     StartClocks();
10826     firstMove = FALSE;
10827
10828     if (appData.autoFlipView && !flipView) {
10829       flipView = !flipView;
10830       DrawPosition(FALSE, NULL);
10831       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10832     }
10833
10834     if(bookHit) { // [HGM] book: simulate book reply
10835         static char bookMove[MSG_SIZ]; // a bit generous?
10836
10837         programStats.nodes = programStats.depth = programStats.time = 
10838         programStats.score = programStats.got_only_move = 0;
10839         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10840
10841         strcpy(bookMove, "move ");
10842         strcat(bookMove, bookHit);
10843         HandleMachineMove(bookMove, &first);
10844     }
10845 }
10846
10847 void
10848 MachineBlackEvent()
10849 {
10850     char buf[MSG_SIZ];
10851    char *bookHit = NULL;
10852
10853     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10854         return;
10855
10856
10857     if (gameMode == PlayFromGameFile || 
10858         gameMode == TwoMachinesPlay  || 
10859         gameMode == Training         || 
10860         gameMode == AnalyzeMode      || 
10861         gameMode == EndOfGame)
10862         EditGameEvent();
10863
10864     if (gameMode == EditPosition) 
10865         EditPositionDone(TRUE);
10866
10867     if (WhiteOnMove(currentMove)) {
10868         DisplayError(_("It is not Black's turn"), 0);
10869         return;
10870     }
10871     
10872     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10873       ExitAnalyzeMode();
10874
10875     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10876         gameMode == AnalyzeFile)
10877         TruncateGame();
10878
10879     ResurrectChessProgram();    /* in case it isn't running */
10880     gameMode = MachinePlaysBlack;
10881     pausing = FALSE;
10882     ModeHighlight();
10883     SetGameInfo();
10884     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10885     DisplayTitle(buf);
10886     if (first.sendName) {
10887       sprintf(buf, "name %s\n", gameInfo.white);
10888       SendToProgram(buf, &first);
10889     }
10890     if (first.sendTime) {
10891       if (first.useColors) {
10892         SendToProgram("white\n", &first); /*gnu kludge*/
10893       }
10894       SendTimeRemaining(&first, FALSE);
10895     }
10896     if (first.useColors) {
10897       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10898     }
10899     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10900     SetMachineThinkingEnables();
10901     first.maybeThinking = TRUE;
10902     StartClocks();
10903
10904     if (appData.autoFlipView && flipView) {
10905       flipView = !flipView;
10906       DrawPosition(FALSE, NULL);
10907       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10908     }
10909     if(bookHit) { // [HGM] book: simulate book reply
10910         static char bookMove[MSG_SIZ]; // a bit generous?
10911
10912         programStats.nodes = programStats.depth = programStats.time = 
10913         programStats.score = programStats.got_only_move = 0;
10914         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10915
10916         strcpy(bookMove, "move ");
10917         strcat(bookMove, bookHit);
10918         HandleMachineMove(bookMove, &first);
10919     }
10920 }
10921
10922
10923 void
10924 DisplayTwoMachinesTitle()
10925 {
10926     char buf[MSG_SIZ];
10927     if (appData.matchGames > 0) {
10928         if (first.twoMachinesColor[0] == 'w') {
10929             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10930                     gameInfo.white, gameInfo.black,
10931                     first.matchWins, second.matchWins,
10932                     matchGame - 1 - (first.matchWins + second.matchWins));
10933         } else {
10934             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10935                     gameInfo.white, gameInfo.black,
10936                     second.matchWins, first.matchWins,
10937                     matchGame - 1 - (first.matchWins + second.matchWins));
10938         }
10939     } else {
10940         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10941     }
10942     DisplayTitle(buf);
10943 }
10944
10945 void
10946 TwoMachinesEvent P((void))
10947 {
10948     int i;
10949     char buf[MSG_SIZ];
10950     ChessProgramState *onmove;
10951     char *bookHit = NULL;
10952     
10953     if (appData.noChessProgram) return;
10954
10955     switch (gameMode) {
10956       case TwoMachinesPlay:
10957         return;
10958       case MachinePlaysWhite:
10959       case MachinePlaysBlack:
10960         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10961             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10962             return;
10963         }
10964         /* fall through */
10965       case BeginningOfGame:
10966       case PlayFromGameFile:
10967       case EndOfGame:
10968         EditGameEvent();
10969         if (gameMode != EditGame) return;
10970         break;
10971       case EditPosition:
10972         EditPositionDone(TRUE);
10973         break;
10974       case AnalyzeMode:
10975       case AnalyzeFile:
10976         ExitAnalyzeMode();
10977         break;
10978       case EditGame:
10979       default:
10980         break;
10981     }
10982
10983 //    forwardMostMove = currentMove;
10984     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10985     ResurrectChessProgram();    /* in case first program isn't running */
10986
10987     if (second.pr == NULL) {
10988         StartChessProgram(&second);
10989         if (second.protocolVersion == 1) {
10990           TwoMachinesEventIfReady();
10991         } else {
10992           /* kludge: allow timeout for initial "feature" command */
10993           FreezeUI();
10994           DisplayMessage("", _("Starting second chess program"));
10995           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10996         }
10997         return;
10998     }
10999     DisplayMessage("", "");
11000     InitChessProgram(&second, FALSE);
11001     SendToProgram("force\n", &second);
11002     if (startedFromSetupPosition) {
11003         SendBoard(&second, backwardMostMove);
11004     if (appData.debugMode) {
11005         fprintf(debugFP, "Two Machines\n");
11006     }
11007     }
11008     for (i = backwardMostMove; i < forwardMostMove; i++) {
11009         SendMoveToProgram(i, &second);
11010     }
11011
11012     gameMode = TwoMachinesPlay;
11013     pausing = FALSE;
11014     ModeHighlight();
11015     SetGameInfo();
11016     DisplayTwoMachinesTitle();
11017     firstMove = TRUE;
11018     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11019         onmove = &first;
11020     } else {
11021         onmove = &second;
11022     }
11023
11024     SendToProgram(first.computerString, &first);
11025     if (first.sendName) {
11026       sprintf(buf, "name %s\n", second.tidy);
11027       SendToProgram(buf, &first);
11028     }
11029     SendToProgram(second.computerString, &second);
11030     if (second.sendName) {
11031       sprintf(buf, "name %s\n", first.tidy);
11032       SendToProgram(buf, &second);
11033     }
11034
11035     ResetClocks();
11036     if (!first.sendTime || !second.sendTime) {
11037         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11038         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11039     }
11040     if (onmove->sendTime) {
11041       if (onmove->useColors) {
11042         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11043       }
11044       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11045     }
11046     if (onmove->useColors) {
11047       SendToProgram(onmove->twoMachinesColor, onmove);
11048     }
11049     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11050 //    SendToProgram("go\n", onmove);
11051     onmove->maybeThinking = TRUE;
11052     SetMachineThinkingEnables();
11053
11054     StartClocks();
11055
11056     if(bookHit) { // [HGM] book: simulate book reply
11057         static char bookMove[MSG_SIZ]; // a bit generous?
11058
11059         programStats.nodes = programStats.depth = programStats.time = 
11060         programStats.score = programStats.got_only_move = 0;
11061         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11062
11063         strcpy(bookMove, "move ");
11064         strcat(bookMove, bookHit);
11065         savedMessage = bookMove; // args for deferred call
11066         savedState = onmove;
11067         ScheduleDelayedEvent(DeferredBookMove, 1);
11068     }
11069 }
11070
11071 void
11072 TrainingEvent()
11073 {
11074     if (gameMode == Training) {
11075       SetTrainingModeOff();
11076       gameMode = PlayFromGameFile;
11077       DisplayMessage("", _("Training mode off"));
11078     } else {
11079       gameMode = Training;
11080       animateTraining = appData.animate;
11081
11082       /* make sure we are not already at the end of the game */
11083       if (currentMove < forwardMostMove) {
11084         SetTrainingModeOn();
11085         DisplayMessage("", _("Training mode on"));
11086       } else {
11087         gameMode = PlayFromGameFile;
11088         DisplayError(_("Already at end of game"), 0);
11089       }
11090     }
11091     ModeHighlight();
11092 }
11093
11094 void
11095 IcsClientEvent()
11096 {
11097     if (!appData.icsActive) return;
11098     switch (gameMode) {
11099       case IcsPlayingWhite:
11100       case IcsPlayingBlack:
11101       case IcsObserving:
11102       case IcsIdle:
11103       case BeginningOfGame:
11104       case IcsExamining:
11105         return;
11106
11107       case EditGame:
11108         break;
11109
11110       case EditPosition:
11111         EditPositionDone(TRUE);
11112         break;
11113
11114       case AnalyzeMode:
11115       case AnalyzeFile:
11116         ExitAnalyzeMode();
11117         break;
11118         
11119       default:
11120         EditGameEvent();
11121         break;
11122     }
11123
11124     gameMode = IcsIdle;
11125     ModeHighlight();
11126     return;
11127 }
11128
11129
11130 void
11131 EditGameEvent()
11132 {
11133     int i;
11134
11135     switch (gameMode) {
11136       case Training:
11137         SetTrainingModeOff();
11138         break;
11139       case MachinePlaysWhite:
11140       case MachinePlaysBlack:
11141       case BeginningOfGame:
11142         SendToProgram("force\n", &first);
11143         SetUserThinkingEnables();
11144         break;
11145       case PlayFromGameFile:
11146         (void) StopLoadGameTimer();
11147         if (gameFileFP != NULL) {
11148             gameFileFP = NULL;
11149         }
11150         break;
11151       case EditPosition:
11152         EditPositionDone(TRUE);
11153         break;
11154       case AnalyzeMode:
11155       case AnalyzeFile:
11156         ExitAnalyzeMode();
11157         SendToProgram("force\n", &first);
11158         break;
11159       case TwoMachinesPlay:
11160         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11161         ResurrectChessProgram();
11162         SetUserThinkingEnables();
11163         break;
11164       case EndOfGame:
11165         ResurrectChessProgram();
11166         break;
11167       case IcsPlayingBlack:
11168       case IcsPlayingWhite:
11169         DisplayError(_("Warning: You are still playing a game"), 0);
11170         break;
11171       case IcsObserving:
11172         DisplayError(_("Warning: You are still observing a game"), 0);
11173         break;
11174       case IcsExamining:
11175         DisplayError(_("Warning: You are still examining a game"), 0);
11176         break;
11177       case IcsIdle:
11178         break;
11179       case EditGame:
11180       default:
11181         return;
11182     }
11183     
11184     pausing = FALSE;
11185     StopClocks();
11186     first.offeredDraw = second.offeredDraw = 0;
11187
11188     if (gameMode == PlayFromGameFile) {
11189         whiteTimeRemaining = timeRemaining[0][currentMove];
11190         blackTimeRemaining = timeRemaining[1][currentMove];
11191         DisplayTitle("");
11192     }
11193
11194     if (gameMode == MachinePlaysWhite ||
11195         gameMode == MachinePlaysBlack ||
11196         gameMode == TwoMachinesPlay ||
11197         gameMode == EndOfGame) {
11198         i = forwardMostMove;
11199         while (i > currentMove) {
11200             SendToProgram("undo\n", &first);
11201             i--;
11202         }
11203         whiteTimeRemaining = timeRemaining[0][currentMove];
11204         blackTimeRemaining = timeRemaining[1][currentMove];
11205         DisplayBothClocks();
11206         if (whiteFlag || blackFlag) {
11207             whiteFlag = blackFlag = 0;
11208         }
11209         DisplayTitle("");
11210     }           
11211     
11212     gameMode = EditGame;
11213     ModeHighlight();
11214     SetGameInfo();
11215 }
11216
11217
11218 void
11219 EditPositionEvent()
11220 {
11221     if (gameMode == EditPosition) {
11222         EditGameEvent();
11223         return;
11224     }
11225     
11226     EditGameEvent();
11227     if (gameMode != EditGame) return;
11228     
11229     gameMode = EditPosition;
11230     ModeHighlight();
11231     SetGameInfo();
11232     if (currentMove > 0)
11233       CopyBoard(boards[0], boards[currentMove]);
11234     
11235     blackPlaysFirst = !WhiteOnMove(currentMove);
11236     ResetClocks();
11237     currentMove = forwardMostMove = backwardMostMove = 0;
11238     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11239     DisplayMove(-1);
11240 }
11241
11242 void
11243 ExitAnalyzeMode()
11244 {
11245     /* [DM] icsEngineAnalyze - possible call from other functions */
11246     if (appData.icsEngineAnalyze) {
11247         appData.icsEngineAnalyze = FALSE;
11248
11249         DisplayMessage("",_("Close ICS engine analyze..."));
11250     }
11251     if (first.analysisSupport && first.analyzing) {
11252       SendToProgram("exit\n", &first);
11253       first.analyzing = FALSE;
11254     }
11255     thinkOutput[0] = NULLCHAR;
11256 }
11257
11258 void
11259 EditPositionDone(Boolean fakeRights)
11260 {
11261     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11262
11263     startedFromSetupPosition = TRUE;
11264     InitChessProgram(&first, FALSE);
11265     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11266       boards[0][EP_STATUS] = EP_NONE;
11267       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11268     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11269         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11270         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11271       } else boards[0][CASTLING][2] = NoRights;
11272     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11273         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11274         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11275       } else boards[0][CASTLING][5] = NoRights;
11276     }
11277     SendToProgram("force\n", &first);
11278     if (blackPlaysFirst) {
11279         strcpy(moveList[0], "");
11280         strcpy(parseList[0], "");
11281         currentMove = forwardMostMove = backwardMostMove = 1;
11282         CopyBoard(boards[1], boards[0]);
11283     } else {
11284         currentMove = forwardMostMove = backwardMostMove = 0;
11285     }
11286     SendBoard(&first, forwardMostMove);
11287     if (appData.debugMode) {
11288         fprintf(debugFP, "EditPosDone\n");
11289     }
11290     DisplayTitle("");
11291     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11292     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11293     gameMode = EditGame;
11294     ModeHighlight();
11295     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11296     ClearHighlights(); /* [AS] */
11297 }
11298
11299 /* Pause for `ms' milliseconds */
11300 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11301 void
11302 TimeDelay(ms)
11303      long ms;
11304 {
11305     TimeMark m1, m2;
11306
11307     GetTimeMark(&m1);
11308     do {
11309         GetTimeMark(&m2);
11310     } while (SubtractTimeMarks(&m2, &m1) < ms);
11311 }
11312
11313 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11314 void
11315 SendMultiLineToICS(buf)
11316      char *buf;
11317 {
11318     char temp[MSG_SIZ+1], *p;
11319     int len;
11320
11321     len = strlen(buf);
11322     if (len > MSG_SIZ)
11323       len = MSG_SIZ;
11324   
11325     strncpy(temp, buf, len);
11326     temp[len] = 0;
11327
11328     p = temp;
11329     while (*p) {
11330         if (*p == '\n' || *p == '\r')
11331           *p = ' ';
11332         ++p;
11333     }
11334
11335     strcat(temp, "\n");
11336     SendToICS(temp);
11337     SendToPlayer(temp, strlen(temp));
11338 }
11339
11340 void
11341 SetWhiteToPlayEvent()
11342 {
11343     if (gameMode == EditPosition) {
11344         blackPlaysFirst = FALSE;
11345         DisplayBothClocks();    /* works because currentMove is 0 */
11346     } else if (gameMode == IcsExamining) {
11347         SendToICS(ics_prefix);
11348         SendToICS("tomove white\n");
11349     }
11350 }
11351
11352 void
11353 SetBlackToPlayEvent()
11354 {
11355     if (gameMode == EditPosition) {
11356         blackPlaysFirst = TRUE;
11357         currentMove = 1;        /* kludge */
11358         DisplayBothClocks();
11359         currentMove = 0;
11360     } else if (gameMode == IcsExamining) {
11361         SendToICS(ics_prefix);
11362         SendToICS("tomove black\n");
11363     }
11364 }
11365
11366 void
11367 EditPositionMenuEvent(selection, x, y)
11368      ChessSquare selection;
11369      int x, y;
11370 {
11371     char buf[MSG_SIZ];
11372     ChessSquare piece = boards[0][y][x];
11373
11374     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11375
11376     switch (selection) {
11377       case ClearBoard:
11378         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11379             SendToICS(ics_prefix);
11380             SendToICS("bsetup clear\n");
11381         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11382             SendToICS(ics_prefix);
11383             SendToICS("clearboard\n");
11384         } else {
11385             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11386                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11387                 for (y = 0; y < BOARD_HEIGHT; y++) {
11388                     if (gameMode == IcsExamining) {
11389                         if (boards[currentMove][y][x] != EmptySquare) {
11390                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11391                                     AAA + x, ONE + y);
11392                             SendToICS(buf);
11393                         }
11394                     } else {
11395                         boards[0][y][x] = p;
11396                     }
11397                 }
11398             }
11399         }
11400         if (gameMode == EditPosition) {
11401             DrawPosition(FALSE, boards[0]);
11402         }
11403         break;
11404
11405       case WhitePlay:
11406         SetWhiteToPlayEvent();
11407         break;
11408
11409       case BlackPlay:
11410         SetBlackToPlayEvent();
11411         break;
11412
11413       case EmptySquare:
11414         if (gameMode == IcsExamining) {
11415             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11416             SendToICS(buf);
11417         } else {
11418             boards[0][y][x] = EmptySquare;
11419             DrawPosition(FALSE, boards[0]);
11420         }
11421         break;
11422
11423       case PromotePiece:
11424         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11425            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11426             selection = (ChessSquare) (PROMOTED piece);
11427         } else if(piece == EmptySquare) selection = WhiteSilver;
11428         else selection = (ChessSquare)((int)piece - 1);
11429         goto defaultlabel;
11430
11431       case DemotePiece:
11432         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11433            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11434             selection = (ChessSquare) (DEMOTED piece);
11435         } else if(piece == EmptySquare) selection = BlackSilver;
11436         else selection = (ChessSquare)((int)piece + 1);       
11437         goto defaultlabel;
11438
11439       case WhiteQueen:
11440       case BlackQueen:
11441         if(gameInfo.variant == VariantShatranj ||
11442            gameInfo.variant == VariantXiangqi  ||
11443            gameInfo.variant == VariantCourier    )
11444             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11445         goto defaultlabel;
11446
11447       case WhiteKing:
11448       case BlackKing:
11449         if(gameInfo.variant == VariantXiangqi)
11450             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11451         if(gameInfo.variant == VariantKnightmate)
11452             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11453       default:
11454         defaultlabel:
11455         if (gameMode == IcsExamining) {
11456             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11457                     PieceToChar(selection), AAA + x, ONE + y);
11458             SendToICS(buf);
11459         } else {
11460             boards[0][y][x] = selection;
11461             DrawPosition(FALSE, boards[0]);
11462         }
11463         break;
11464     }
11465 }
11466
11467
11468 void
11469 DropMenuEvent(selection, x, y)
11470      ChessSquare selection;
11471      int x, y;
11472 {
11473     ChessMove moveType;
11474
11475     switch (gameMode) {
11476       case IcsPlayingWhite:
11477       case MachinePlaysBlack:
11478         if (!WhiteOnMove(currentMove)) {
11479             DisplayMoveError(_("It is Black's turn"));
11480             return;
11481         }
11482         moveType = WhiteDrop;
11483         break;
11484       case IcsPlayingBlack:
11485       case MachinePlaysWhite:
11486         if (WhiteOnMove(currentMove)) {
11487             DisplayMoveError(_("It is White's turn"));
11488             return;
11489         }
11490         moveType = BlackDrop;
11491         break;
11492       case EditGame:
11493         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11494         break;
11495       default:
11496         return;
11497     }
11498
11499     if (moveType == BlackDrop && selection < BlackPawn) {
11500       selection = (ChessSquare) ((int) selection
11501                                  + (int) BlackPawn - (int) WhitePawn);
11502     }
11503     if (boards[currentMove][y][x] != EmptySquare) {
11504         DisplayMoveError(_("That square is occupied"));
11505         return;
11506     }
11507
11508     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11509 }
11510
11511 void
11512 AcceptEvent()
11513 {
11514     /* Accept a pending offer of any kind from opponent */
11515     
11516     if (appData.icsActive) {
11517         SendToICS(ics_prefix);
11518         SendToICS("accept\n");
11519     } else if (cmailMsgLoaded) {
11520         if (currentMove == cmailOldMove &&
11521             commentList[cmailOldMove] != NULL &&
11522             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11523                    "Black offers a draw" : "White offers a draw")) {
11524             TruncateGame();
11525             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11527         } else {
11528             DisplayError(_("There is no pending offer on this move"), 0);
11529             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11530         }
11531     } else {
11532         /* Not used for offers from chess program */
11533     }
11534 }
11535
11536 void
11537 DeclineEvent()
11538 {
11539     /* Decline a pending offer of any kind from opponent */
11540     
11541     if (appData.icsActive) {
11542         SendToICS(ics_prefix);
11543         SendToICS("decline\n");
11544     } else if (cmailMsgLoaded) {
11545         if (currentMove == cmailOldMove &&
11546             commentList[cmailOldMove] != NULL &&
11547             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11548                    "Black offers a draw" : "White offers a draw")) {
11549 #ifdef NOTDEF
11550             AppendComment(cmailOldMove, "Draw declined", TRUE);
11551             DisplayComment(cmailOldMove - 1, "Draw declined");
11552 #endif /*NOTDEF*/
11553         } else {
11554             DisplayError(_("There is no pending offer on this move"), 0);
11555         }
11556     } else {
11557         /* Not used for offers from chess program */
11558     }
11559 }
11560
11561 void
11562 RematchEvent()
11563 {
11564     /* Issue ICS rematch command */
11565     if (appData.icsActive) {
11566         SendToICS(ics_prefix);
11567         SendToICS("rematch\n");
11568     }
11569 }
11570
11571 void
11572 CallFlagEvent()
11573 {
11574     /* Call your opponent's flag (claim a win on time) */
11575     if (appData.icsActive) {
11576         SendToICS(ics_prefix);
11577         SendToICS("flag\n");
11578     } else {
11579         switch (gameMode) {
11580           default:
11581             return;
11582           case MachinePlaysWhite:
11583             if (whiteFlag) {
11584                 if (blackFlag)
11585                   GameEnds(GameIsDrawn, "Both players ran out of time",
11586                            GE_PLAYER);
11587                 else
11588                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11589             } else {
11590                 DisplayError(_("Your opponent is not out of time"), 0);
11591             }
11592             break;
11593           case MachinePlaysBlack:
11594             if (blackFlag) {
11595                 if (whiteFlag)
11596                   GameEnds(GameIsDrawn, "Both players ran out of time",
11597                            GE_PLAYER);
11598                 else
11599                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11600             } else {
11601                 DisplayError(_("Your opponent is not out of time"), 0);
11602             }
11603             break;
11604         }
11605     }
11606 }
11607
11608 void
11609 DrawEvent()
11610 {
11611     /* Offer draw or accept pending draw offer from opponent */
11612     
11613     if (appData.icsActive) {
11614         /* Note: tournament rules require draw offers to be
11615            made after you make your move but before you punch
11616            your clock.  Currently ICS doesn't let you do that;
11617            instead, you immediately punch your clock after making
11618            a move, but you can offer a draw at any time. */
11619         
11620         SendToICS(ics_prefix);
11621         SendToICS("draw\n");
11622     } else if (cmailMsgLoaded) {
11623         if (currentMove == cmailOldMove &&
11624             commentList[cmailOldMove] != NULL &&
11625             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11626                    "Black offers a draw" : "White offers a draw")) {
11627             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11628             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11629         } else if (currentMove == cmailOldMove + 1) {
11630             char *offer = WhiteOnMove(cmailOldMove) ?
11631               "White offers a draw" : "Black offers a draw";
11632             AppendComment(currentMove, offer, TRUE);
11633             DisplayComment(currentMove - 1, offer);
11634             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11635         } else {
11636             DisplayError(_("You must make your move before offering a draw"), 0);
11637             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11638         }
11639     } else if (first.offeredDraw) {
11640         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11641     } else {
11642         if (first.sendDrawOffers) {
11643             SendToProgram("draw\n", &first);
11644             userOfferedDraw = TRUE;
11645         }
11646     }
11647 }
11648
11649 void
11650 AdjournEvent()
11651 {
11652     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11653     
11654     if (appData.icsActive) {
11655         SendToICS(ics_prefix);
11656         SendToICS("adjourn\n");
11657     } else {
11658         /* Currently GNU Chess doesn't offer or accept Adjourns */
11659     }
11660 }
11661
11662
11663 void
11664 AbortEvent()
11665 {
11666     /* Offer Abort or accept pending Abort offer from opponent */
11667     
11668     if (appData.icsActive) {
11669         SendToICS(ics_prefix);
11670         SendToICS("abort\n");
11671     } else {
11672         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11673     }
11674 }
11675
11676 void
11677 ResignEvent()
11678 {
11679     /* Resign.  You can do this even if it's not your turn. */
11680     
11681     if (appData.icsActive) {
11682         SendToICS(ics_prefix);
11683         SendToICS("resign\n");
11684     } else {
11685         switch (gameMode) {
11686           case MachinePlaysWhite:
11687             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11688             break;
11689           case MachinePlaysBlack:
11690             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11691             break;
11692           case EditGame:
11693             if (cmailMsgLoaded) {
11694                 TruncateGame();
11695                 if (WhiteOnMove(cmailOldMove)) {
11696                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11697                 } else {
11698                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11699                 }
11700                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11701             }
11702             break;
11703           default:
11704             break;
11705         }
11706     }
11707 }
11708
11709
11710 void
11711 StopObservingEvent()
11712 {
11713     /* Stop observing current games */
11714     SendToICS(ics_prefix);
11715     SendToICS("unobserve\n");
11716 }
11717
11718 void
11719 StopExaminingEvent()
11720 {
11721     /* Stop observing current game */
11722     SendToICS(ics_prefix);
11723     SendToICS("unexamine\n");
11724 }
11725
11726 void
11727 ForwardInner(target)
11728      int target;
11729 {
11730     int limit;
11731
11732     if (appData.debugMode)
11733         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11734                 target, currentMove, forwardMostMove);
11735
11736     if (gameMode == EditPosition)
11737       return;
11738
11739     if (gameMode == PlayFromGameFile && !pausing)
11740       PauseEvent();
11741     
11742     if (gameMode == IcsExamining && pausing)
11743       limit = pauseExamForwardMostMove;
11744     else
11745       limit = forwardMostMove;
11746     
11747     if (target > limit) target = limit;
11748
11749     if (target > 0 && moveList[target - 1][0]) {
11750         int fromX, fromY, toX, toY;
11751         toX = moveList[target - 1][2] - AAA;
11752         toY = moveList[target - 1][3] - ONE;
11753         if (moveList[target - 1][1] == '@') {
11754             if (appData.highlightLastMove) {
11755                 SetHighlights(-1, -1, toX, toY);
11756             }
11757         } else {
11758             fromX = moveList[target - 1][0] - AAA;
11759             fromY = moveList[target - 1][1] - ONE;
11760             if (target == currentMove + 1) {
11761                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11762             }
11763             if (appData.highlightLastMove) {
11764                 SetHighlights(fromX, fromY, toX, toY);
11765             }
11766         }
11767     }
11768     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11769         gameMode == Training || gameMode == PlayFromGameFile || 
11770         gameMode == AnalyzeFile) {
11771         while (currentMove < target) {
11772             SendMoveToProgram(currentMove++, &first);
11773         }
11774     } else {
11775         currentMove = target;
11776     }
11777     
11778     if (gameMode == EditGame || gameMode == EndOfGame) {
11779         whiteTimeRemaining = timeRemaining[0][currentMove];
11780         blackTimeRemaining = timeRemaining[1][currentMove];
11781     }
11782     DisplayBothClocks();
11783     DisplayMove(currentMove - 1);
11784     DrawPosition(FALSE, boards[currentMove]);
11785     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11786     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11787         DisplayComment(currentMove - 1, commentList[currentMove]);
11788     }
11789 }
11790
11791
11792 void
11793 ForwardEvent()
11794 {
11795     if (gameMode == IcsExamining && !pausing) {
11796         SendToICS(ics_prefix);
11797         SendToICS("forward\n");
11798     } else {
11799         ForwardInner(currentMove + 1);
11800     }
11801 }
11802
11803 void
11804 ToEndEvent()
11805 {
11806     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11807         /* to optimze, we temporarily turn off analysis mode while we feed
11808          * the remaining moves to the engine. Otherwise we get analysis output
11809          * after each move.
11810          */ 
11811         if (first.analysisSupport) {
11812           SendToProgram("exit\nforce\n", &first);
11813           first.analyzing = FALSE;
11814         }
11815     }
11816         
11817     if (gameMode == IcsExamining && !pausing) {
11818         SendToICS(ics_prefix);
11819         SendToICS("forward 999999\n");
11820     } else {
11821         ForwardInner(forwardMostMove);
11822     }
11823
11824     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11825         /* we have fed all the moves, so reactivate analysis mode */
11826         SendToProgram("analyze\n", &first);
11827         first.analyzing = TRUE;
11828         /*first.maybeThinking = TRUE;*/
11829         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11830     }
11831 }
11832
11833 void
11834 BackwardInner(target)
11835      int target;
11836 {
11837     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11838
11839     if (appData.debugMode)
11840         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11841                 target, currentMove, forwardMostMove);
11842
11843     if (gameMode == EditPosition) return;
11844     if (currentMove <= backwardMostMove) {
11845         ClearHighlights();
11846         DrawPosition(full_redraw, boards[currentMove]);
11847         return;
11848     }
11849     if (gameMode == PlayFromGameFile && !pausing)
11850       PauseEvent();
11851     
11852     if (moveList[target][0]) {
11853         int fromX, fromY, toX, toY;
11854         toX = moveList[target][2] - AAA;
11855         toY = moveList[target][3] - ONE;
11856         if (moveList[target][1] == '@') {
11857             if (appData.highlightLastMove) {
11858                 SetHighlights(-1, -1, toX, toY);
11859             }
11860         } else {
11861             fromX = moveList[target][0] - AAA;
11862             fromY = moveList[target][1] - ONE;
11863             if (target == currentMove - 1) {
11864                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11865             }
11866             if (appData.highlightLastMove) {
11867                 SetHighlights(fromX, fromY, toX, toY);
11868             }
11869         }
11870     }
11871     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11872         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11873         while (currentMove > target) {
11874             SendToProgram("undo\n", &first);
11875             currentMove--;
11876         }
11877     } else {
11878         currentMove = target;
11879     }
11880     
11881     if (gameMode == EditGame || gameMode == EndOfGame) {
11882         whiteTimeRemaining = timeRemaining[0][currentMove];
11883         blackTimeRemaining = timeRemaining[1][currentMove];
11884     }
11885     DisplayBothClocks();
11886     DisplayMove(currentMove - 1);
11887     DrawPosition(full_redraw, boards[currentMove]);
11888     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11889     // [HGM] PV info: routine tests if comment empty
11890     DisplayComment(currentMove - 1, commentList[currentMove]);
11891 }
11892
11893 void
11894 BackwardEvent()
11895 {
11896     if (gameMode == IcsExamining && !pausing) {
11897         SendToICS(ics_prefix);
11898         SendToICS("backward\n");
11899     } else {
11900         BackwardInner(currentMove - 1);
11901     }
11902 }
11903
11904 void
11905 ToStartEvent()
11906 {
11907     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11908         /* to optimize, we temporarily turn off analysis mode while we undo
11909          * all the moves. Otherwise we get analysis output after each undo.
11910          */ 
11911         if (first.analysisSupport) {
11912           SendToProgram("exit\nforce\n", &first);
11913           first.analyzing = FALSE;
11914         }
11915     }
11916
11917     if (gameMode == IcsExamining && !pausing) {
11918         SendToICS(ics_prefix);
11919         SendToICS("backward 999999\n");
11920     } else {
11921         BackwardInner(backwardMostMove);
11922     }
11923
11924     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11925         /* we have fed all the moves, so reactivate analysis mode */
11926         SendToProgram("analyze\n", &first);
11927         first.analyzing = TRUE;
11928         /*first.maybeThinking = TRUE;*/
11929         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11930     }
11931 }
11932
11933 void
11934 ToNrEvent(int to)
11935 {
11936   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11937   if (to >= forwardMostMove) to = forwardMostMove;
11938   if (to <= backwardMostMove) to = backwardMostMove;
11939   if (to < currentMove) {
11940     BackwardInner(to);
11941   } else {
11942     ForwardInner(to);
11943   }
11944 }
11945
11946 void
11947 RevertEvent()
11948 {
11949     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11950         return;
11951     }
11952     if (gameMode != IcsExamining) {
11953         DisplayError(_("You are not examining a game"), 0);
11954         return;
11955     }
11956     if (pausing) {
11957         DisplayError(_("You can't revert while pausing"), 0);
11958         return;
11959     }
11960     SendToICS(ics_prefix);
11961     SendToICS("revert\n");
11962 }
11963
11964 void
11965 RetractMoveEvent()
11966 {
11967     switch (gameMode) {
11968       case MachinePlaysWhite:
11969       case MachinePlaysBlack:
11970         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11971             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11972             return;
11973         }
11974         if (forwardMostMove < 2) return;
11975         currentMove = forwardMostMove = forwardMostMove - 2;
11976         whiteTimeRemaining = timeRemaining[0][currentMove];
11977         blackTimeRemaining = timeRemaining[1][currentMove];
11978         DisplayBothClocks();
11979         DisplayMove(currentMove - 1);
11980         ClearHighlights();/*!! could figure this out*/
11981         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11982         SendToProgram("remove\n", &first);
11983         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11984         break;
11985
11986       case BeginningOfGame:
11987       default:
11988         break;
11989
11990       case IcsPlayingWhite:
11991       case IcsPlayingBlack:
11992         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11993             SendToICS(ics_prefix);
11994             SendToICS("takeback 2\n");
11995         } else {
11996             SendToICS(ics_prefix);
11997             SendToICS("takeback 1\n");
11998         }
11999         break;
12000     }
12001 }
12002
12003 void
12004 MoveNowEvent()
12005 {
12006     ChessProgramState *cps;
12007
12008     switch (gameMode) {
12009       case MachinePlaysWhite:
12010         if (!WhiteOnMove(forwardMostMove)) {
12011             DisplayError(_("It is your turn"), 0);
12012             return;
12013         }
12014         cps = &first;
12015         break;
12016       case MachinePlaysBlack:
12017         if (WhiteOnMove(forwardMostMove)) {
12018             DisplayError(_("It is your turn"), 0);
12019             return;
12020         }
12021         cps = &first;
12022         break;
12023       case TwoMachinesPlay:
12024         if (WhiteOnMove(forwardMostMove) ==
12025             (first.twoMachinesColor[0] == 'w')) {
12026             cps = &first;
12027         } else {
12028             cps = &second;
12029         }
12030         break;
12031       case BeginningOfGame:
12032       default:
12033         return;
12034     }
12035     SendToProgram("?\n", cps);
12036 }
12037
12038 void
12039 TruncateGameEvent()
12040 {
12041     EditGameEvent();
12042     if (gameMode != EditGame) return;
12043     TruncateGame();
12044 }
12045
12046 void
12047 TruncateGame()
12048 {
12049     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12050     if (forwardMostMove > currentMove) {
12051         if (gameInfo.resultDetails != NULL) {
12052             free(gameInfo.resultDetails);
12053             gameInfo.resultDetails = NULL;
12054             gameInfo.result = GameUnfinished;
12055         }
12056         forwardMostMove = currentMove;
12057         HistorySet(parseList, backwardMostMove, forwardMostMove,
12058                    currentMove-1);
12059     }
12060 }
12061
12062 void
12063 HintEvent()
12064 {
12065     if (appData.noChessProgram) return;
12066     switch (gameMode) {
12067       case MachinePlaysWhite:
12068         if (WhiteOnMove(forwardMostMove)) {
12069             DisplayError(_("Wait until your turn"), 0);
12070             return;
12071         }
12072         break;
12073       case BeginningOfGame:
12074       case MachinePlaysBlack:
12075         if (!WhiteOnMove(forwardMostMove)) {
12076             DisplayError(_("Wait until your turn"), 0);
12077             return;
12078         }
12079         break;
12080       default:
12081         DisplayError(_("No hint available"), 0);
12082         return;
12083     }
12084     SendToProgram("hint\n", &first);
12085     hintRequested = TRUE;
12086 }
12087
12088 void
12089 BookEvent()
12090 {
12091     if (appData.noChessProgram) return;
12092     switch (gameMode) {
12093       case MachinePlaysWhite:
12094         if (WhiteOnMove(forwardMostMove)) {
12095             DisplayError(_("Wait until your turn"), 0);
12096             return;
12097         }
12098         break;
12099       case BeginningOfGame:
12100       case MachinePlaysBlack:
12101         if (!WhiteOnMove(forwardMostMove)) {
12102             DisplayError(_("Wait until your turn"), 0);
12103             return;
12104         }
12105         break;
12106       case EditPosition:
12107         EditPositionDone(TRUE);
12108         break;
12109       case TwoMachinesPlay:
12110         return;
12111       default:
12112         break;
12113     }
12114     SendToProgram("bk\n", &first);
12115     bookOutput[0] = NULLCHAR;
12116     bookRequested = TRUE;
12117 }
12118
12119 void
12120 AboutGameEvent()
12121 {
12122     char *tags = PGNTags(&gameInfo);
12123     TagsPopUp(tags, CmailMsg());
12124     free(tags);
12125 }
12126
12127 /* end button procedures */
12128
12129 void
12130 PrintPosition(fp, move)
12131      FILE *fp;
12132      int move;
12133 {
12134     int i, j;
12135     
12136     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12137         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12138             char c = PieceToChar(boards[move][i][j]);
12139             fputc(c == 'x' ? '.' : c, fp);
12140             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12141         }
12142     }
12143     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12144       fprintf(fp, "white to play\n");
12145     else
12146       fprintf(fp, "black to play\n");
12147 }
12148
12149 void
12150 PrintOpponents(fp)
12151      FILE *fp;
12152 {
12153     if (gameInfo.white != NULL) {
12154         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12155     } else {
12156         fprintf(fp, "\n");
12157     }
12158 }
12159
12160 /* Find last component of program's own name, using some heuristics */
12161 void
12162 TidyProgramName(prog, host, buf)
12163      char *prog, *host, buf[MSG_SIZ];
12164 {
12165     char *p, *q;
12166     int local = (strcmp(host, "localhost") == 0);
12167     while (!local && (p = strchr(prog, ';')) != NULL) {
12168         p++;
12169         while (*p == ' ') p++;
12170         prog = p;
12171     }
12172     if (*prog == '"' || *prog == '\'') {
12173         q = strchr(prog + 1, *prog);
12174     } else {
12175         q = strchr(prog, ' ');
12176     }
12177     if (q == NULL) q = prog + strlen(prog);
12178     p = q;
12179     while (p >= prog && *p != '/' && *p != '\\') p--;
12180     p++;
12181     if(p == prog && *p == '"') p++;
12182     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12183     memcpy(buf, p, q - p);
12184     buf[q - p] = NULLCHAR;
12185     if (!local) {
12186         strcat(buf, "@");
12187         strcat(buf, host);
12188     }
12189 }
12190
12191 char *
12192 TimeControlTagValue()
12193 {
12194     char buf[MSG_SIZ];
12195     if (!appData.clockMode) {
12196         strcpy(buf, "-");
12197     } else if (movesPerSession > 0) {
12198         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12199     } else if (timeIncrement == 0) {
12200         sprintf(buf, "%ld", timeControl/1000);
12201     } else {
12202         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12203     }
12204     return StrSave(buf);
12205 }
12206
12207 void
12208 SetGameInfo()
12209 {
12210     /* This routine is used only for certain modes */
12211     VariantClass v = gameInfo.variant;
12212     ChessMove r = GameUnfinished;
12213     char *p = NULL;
12214
12215     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12216         r = gameInfo.result; 
12217         p = gameInfo.resultDetails; 
12218         gameInfo.resultDetails = NULL;
12219     }
12220     ClearGameInfo(&gameInfo);
12221     gameInfo.variant = v;
12222
12223     switch (gameMode) {
12224       case MachinePlaysWhite:
12225         gameInfo.event = StrSave( appData.pgnEventHeader );
12226         gameInfo.site = StrSave(HostName());
12227         gameInfo.date = PGNDate();
12228         gameInfo.round = StrSave("-");
12229         gameInfo.white = StrSave(first.tidy);
12230         gameInfo.black = StrSave(UserName());
12231         gameInfo.timeControl = TimeControlTagValue();
12232         break;
12233
12234       case MachinePlaysBlack:
12235         gameInfo.event = StrSave( appData.pgnEventHeader );
12236         gameInfo.site = StrSave(HostName());
12237         gameInfo.date = PGNDate();
12238         gameInfo.round = StrSave("-");
12239         gameInfo.white = StrSave(UserName());
12240         gameInfo.black = StrSave(first.tidy);
12241         gameInfo.timeControl = TimeControlTagValue();
12242         break;
12243
12244       case TwoMachinesPlay:
12245         gameInfo.event = StrSave( appData.pgnEventHeader );
12246         gameInfo.site = StrSave(HostName());
12247         gameInfo.date = PGNDate();
12248         if (matchGame > 0) {
12249             char buf[MSG_SIZ];
12250             sprintf(buf, "%d", matchGame);
12251             gameInfo.round = StrSave(buf);
12252         } else {
12253             gameInfo.round = StrSave("-");
12254         }
12255         if (first.twoMachinesColor[0] == 'w') {
12256             gameInfo.white = StrSave(first.tidy);
12257             gameInfo.black = StrSave(second.tidy);
12258         } else {
12259             gameInfo.white = StrSave(second.tidy);
12260             gameInfo.black = StrSave(first.tidy);
12261         }
12262         gameInfo.timeControl = TimeControlTagValue();
12263         break;
12264
12265       case EditGame:
12266         gameInfo.event = StrSave("Edited game");
12267         gameInfo.site = StrSave(HostName());
12268         gameInfo.date = PGNDate();
12269         gameInfo.round = StrSave("-");
12270         gameInfo.white = StrSave("-");
12271         gameInfo.black = StrSave("-");
12272         gameInfo.result = r;
12273         gameInfo.resultDetails = p;
12274         break;
12275
12276       case EditPosition:
12277         gameInfo.event = StrSave("Edited position");
12278         gameInfo.site = StrSave(HostName());
12279         gameInfo.date = PGNDate();
12280         gameInfo.round = StrSave("-");
12281         gameInfo.white = StrSave("-");
12282         gameInfo.black = StrSave("-");
12283         break;
12284
12285       case IcsPlayingWhite:
12286       case IcsPlayingBlack:
12287       case IcsObserving:
12288       case IcsExamining:
12289         break;
12290
12291       case PlayFromGameFile:
12292         gameInfo.event = StrSave("Game from non-PGN file");
12293         gameInfo.site = StrSave(HostName());
12294         gameInfo.date = PGNDate();
12295         gameInfo.round = StrSave("-");
12296         gameInfo.white = StrSave("?");
12297         gameInfo.black = StrSave("?");
12298         break;
12299
12300       default:
12301         break;
12302     }
12303 }
12304
12305 void
12306 ReplaceComment(index, text)
12307      int index;
12308      char *text;
12309 {
12310     int len;
12311
12312     while (*text == '\n') text++;
12313     len = strlen(text);
12314     while (len > 0 && text[len - 1] == '\n') len--;
12315
12316     if (commentList[index] != NULL)
12317       free(commentList[index]);
12318
12319     if (len == 0) {
12320         commentList[index] = NULL;
12321         return;
12322     }
12323   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12324       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12325       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12326     commentList[index] = (char *) malloc(len + 2);
12327     strncpy(commentList[index], text, len);
12328     commentList[index][len] = '\n';
12329     commentList[index][len + 1] = NULLCHAR;
12330   } else { 
12331     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12332     char *p;
12333     commentList[index] = (char *) malloc(len + 6);
12334     strcpy(commentList[index], "{\n");
12335     strncpy(commentList[index]+2, text, len);
12336     commentList[index][len+2] = NULLCHAR;
12337     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12338     strcat(commentList[index], "\n}\n");
12339   }
12340 }
12341
12342 void
12343 CrushCRs(text)
12344      char *text;
12345 {
12346   char *p = text;
12347   char *q = text;
12348   char ch;
12349
12350   do {
12351     ch = *p++;
12352     if (ch == '\r') continue;
12353     *q++ = ch;
12354   } while (ch != '\0');
12355 }
12356
12357 void
12358 AppendComment(index, text, addBraces)
12359      int index;
12360      char *text;
12361      Boolean addBraces; // [HGM] braces: tells if we should add {}
12362 {
12363     int oldlen, len;
12364     char *old;
12365
12366 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12367     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12368
12369     CrushCRs(text);
12370     while (*text == '\n') text++;
12371     len = strlen(text);
12372     while (len > 0 && text[len - 1] == '\n') len--;
12373
12374     if (len == 0) return;
12375
12376     if (commentList[index] != NULL) {
12377         old = commentList[index];
12378         oldlen = strlen(old);
12379         while(commentList[index][oldlen-1] ==  '\n')
12380           commentList[index][--oldlen] = NULLCHAR;
12381         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12382         strcpy(commentList[index], old);
12383         free(old);
12384         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12385         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12386           if(addBraces) addBraces = FALSE; else { text++; len--; }
12387           while (*text == '\n') { text++; len--; }
12388           commentList[index][--oldlen] = NULLCHAR;
12389       }
12390         if(addBraces) strcat(commentList[index], "\n{\n");
12391         else          strcat(commentList[index], "\n");
12392         strcat(commentList[index], text);
12393         if(addBraces) strcat(commentList[index], "\n}\n");
12394         else          strcat(commentList[index], "\n");
12395     } else {
12396         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12397         if(addBraces)
12398              strcpy(commentList[index], "{\n");
12399         else commentList[index][0] = NULLCHAR;
12400         strcat(commentList[index], text);
12401         strcat(commentList[index], "\n");
12402         if(addBraces) strcat(commentList[index], "}\n");
12403     }
12404 }
12405
12406 static char * FindStr( char * text, char * sub_text )
12407 {
12408     char * result = strstr( text, sub_text );
12409
12410     if( result != NULL ) {
12411         result += strlen( sub_text );
12412     }
12413
12414     return result;
12415 }
12416
12417 /* [AS] Try to extract PV info from PGN comment */
12418 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12419 char *GetInfoFromComment( int index, char * text )
12420 {
12421     char * sep = text;
12422
12423     if( text != NULL && index > 0 ) {
12424         int score = 0;
12425         int depth = 0;
12426         int time = -1, sec = 0, deci;
12427         char * s_eval = FindStr( text, "[%eval " );
12428         char * s_emt = FindStr( text, "[%emt " );
12429
12430         if( s_eval != NULL || s_emt != NULL ) {
12431             /* New style */
12432             char delim;
12433
12434             if( s_eval != NULL ) {
12435                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12436                     return text;
12437                 }
12438
12439                 if( delim != ']' ) {
12440                     return text;
12441                 }
12442             }
12443
12444             if( s_emt != NULL ) {
12445             }
12446                 return text;
12447         }
12448         else {
12449             /* We expect something like: [+|-]nnn.nn/dd */
12450             int score_lo = 0;
12451
12452             if(*text != '{') return text; // [HGM] braces: must be normal comment
12453
12454             sep = strchr( text, '/' );
12455             if( sep == NULL || sep < (text+4) ) {
12456                 return text;
12457             }
12458
12459             time = -1; sec = -1; deci = -1;
12460             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12461                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12462                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12463                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12464                 return text;
12465             }
12466
12467             if( score_lo < 0 || score_lo >= 100 ) {
12468                 return text;
12469             }
12470
12471             if(sec >= 0) time = 600*time + 10*sec; else
12472             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12473
12474             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12475
12476             /* [HGM] PV time: now locate end of PV info */
12477             while( *++sep >= '0' && *sep <= '9'); // strip depth
12478             if(time >= 0)
12479             while( *++sep >= '0' && *sep <= '9'); // strip time
12480             if(sec >= 0)
12481             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12482             if(deci >= 0)
12483             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12484             while(*sep == ' ') sep++;
12485         }
12486
12487         if( depth <= 0 ) {
12488             return text;
12489         }
12490
12491         if( time < 0 ) {
12492             time = -1;
12493         }
12494
12495         pvInfoList[index-1].depth = depth;
12496         pvInfoList[index-1].score = score;
12497         pvInfoList[index-1].time  = 10*time; // centi-sec
12498         if(*sep == '}') *sep = 0; else *--sep = '{';
12499     }
12500     return sep;
12501 }
12502
12503 void
12504 SendToProgram(message, cps)
12505      char *message;
12506      ChessProgramState *cps;
12507 {
12508     int count, outCount, error;
12509     char buf[MSG_SIZ];
12510
12511     if (cps->pr == NULL) return;
12512     Attention(cps);
12513     
12514     if (appData.debugMode) {
12515         TimeMark now;
12516         GetTimeMark(&now);
12517         fprintf(debugFP, "%ld >%-6s: %s", 
12518                 SubtractTimeMarks(&now, &programStartTime),
12519                 cps->which, message);
12520     }
12521     
12522     count = strlen(message);
12523     outCount = OutputToProcess(cps->pr, message, count, &error);
12524     if (outCount < count && !exiting 
12525                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12526         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12527         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12528             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12529                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12530                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12531             } else {
12532                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12533             }
12534             gameInfo.resultDetails = StrSave(buf);
12535         }
12536         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12537     }
12538 }
12539
12540 void
12541 ReceiveFromProgram(isr, closure, message, count, error)
12542      InputSourceRef isr;
12543      VOIDSTAR closure;
12544      char *message;
12545      int count;
12546      int error;
12547 {
12548     char *end_str;
12549     char buf[MSG_SIZ];
12550     ChessProgramState *cps = (ChessProgramState *)closure;
12551
12552     if (isr != cps->isr) return; /* Killed intentionally */
12553     if (count <= 0) {
12554         if (count == 0) {
12555             sprintf(buf,
12556                     _("Error: %s chess program (%s) exited unexpectedly"),
12557                     cps->which, cps->program);
12558         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12559                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12560                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12561                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12562                 } else {
12563                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12564                 }
12565                 gameInfo.resultDetails = StrSave(buf);
12566             }
12567             RemoveInputSource(cps->isr);
12568             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12569         } else {
12570             sprintf(buf,
12571                     _("Error reading from %s chess program (%s)"),
12572                     cps->which, cps->program);
12573             RemoveInputSource(cps->isr);
12574
12575             /* [AS] Program is misbehaving badly... kill it */
12576             if( count == -2 ) {
12577                 DestroyChildProcess( cps->pr, 9 );
12578                 cps->pr = NoProc;
12579             }
12580
12581             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12582         }
12583         return;
12584     }
12585     
12586     if ((end_str = strchr(message, '\r')) != NULL)
12587       *end_str = NULLCHAR;
12588     if ((end_str = strchr(message, '\n')) != NULL)
12589       *end_str = NULLCHAR;
12590     
12591     if (appData.debugMode) {
12592         TimeMark now; int print = 1;
12593         char *quote = ""; char c; int i;
12594
12595         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12596                 char start = message[0];
12597                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12598                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12599                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12600                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12601                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12602                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12603                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12604                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12605                         { quote = "# "; print = (appData.engineComments == 2); }
12606                 message[0] = start; // restore original message
12607         }
12608         if(print) {
12609                 GetTimeMark(&now);
12610                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12611                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12612                         quote,
12613                         message);
12614         }
12615     }
12616
12617     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12618     if (appData.icsEngineAnalyze) {
12619         if (strstr(message, "whisper") != NULL ||
12620              strstr(message, "kibitz") != NULL || 
12621             strstr(message, "tellics") != NULL) return;
12622     }
12623
12624     HandleMachineMove(message, cps);
12625 }
12626
12627
12628 void
12629 SendTimeControl(cps, mps, tc, inc, sd, st)
12630      ChessProgramState *cps;
12631      int mps, inc, sd, st;
12632      long tc;
12633 {
12634     char buf[MSG_SIZ];
12635     int seconds;
12636
12637     if( timeControl_2 > 0 ) {
12638         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12639             tc = timeControl_2;
12640         }
12641     }
12642     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12643     inc /= cps->timeOdds;
12644     st  /= cps->timeOdds;
12645
12646     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12647
12648     if (st > 0) {
12649       /* Set exact time per move, normally using st command */
12650       if (cps->stKludge) {
12651         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12652         seconds = st % 60;
12653         if (seconds == 0) {
12654           sprintf(buf, "level 1 %d\n", st/60);
12655         } else {
12656           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12657         }
12658       } else {
12659         sprintf(buf, "st %d\n", st);
12660       }
12661     } else {
12662       /* Set conventional or incremental time control, using level command */
12663       if (seconds == 0) {
12664         /* Note old gnuchess bug -- minutes:seconds used to not work.
12665            Fixed in later versions, but still avoid :seconds
12666            when seconds is 0. */
12667         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12668       } else {
12669         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12670                 seconds, inc/1000);
12671       }
12672     }
12673     SendToProgram(buf, cps);
12674
12675     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12676     /* Orthogonally, limit search to given depth */
12677     if (sd > 0) {
12678       if (cps->sdKludge) {
12679         sprintf(buf, "depth\n%d\n", sd);
12680       } else {
12681         sprintf(buf, "sd %d\n", sd);
12682       }
12683       SendToProgram(buf, cps);
12684     }
12685
12686     if(cps->nps > 0) { /* [HGM] nps */
12687         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12688         else {
12689                 sprintf(buf, "nps %d\n", cps->nps);
12690               SendToProgram(buf, cps);
12691         }
12692     }
12693 }
12694
12695 ChessProgramState *WhitePlayer()
12696 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12697 {
12698     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12699        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12700         return &second;
12701     return &first;
12702 }
12703
12704 void
12705 SendTimeRemaining(cps, machineWhite)
12706      ChessProgramState *cps;
12707      int /*boolean*/ machineWhite;
12708 {
12709     char message[MSG_SIZ];
12710     long time, otime;
12711
12712     /* Note: this routine must be called when the clocks are stopped
12713        or when they have *just* been set or switched; otherwise
12714        it will be off by the time since the current tick started.
12715     */
12716     if (machineWhite) {
12717         time = whiteTimeRemaining / 10;
12718         otime = blackTimeRemaining / 10;
12719     } else {
12720         time = blackTimeRemaining / 10;
12721         otime = whiteTimeRemaining / 10;
12722     }
12723     /* [HGM] translate opponent's time by time-odds factor */
12724     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12725     if (appData.debugMode) {
12726         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12727     }
12728
12729     if (time <= 0) time = 1;
12730     if (otime <= 0) otime = 1;
12731     
12732     sprintf(message, "time %ld\n", time);
12733     SendToProgram(message, cps);
12734
12735     sprintf(message, "otim %ld\n", otime);
12736     SendToProgram(message, cps);
12737 }
12738
12739 int
12740 BoolFeature(p, name, loc, cps)
12741      char **p;
12742      char *name;
12743      int *loc;
12744      ChessProgramState *cps;
12745 {
12746   char buf[MSG_SIZ];
12747   int len = strlen(name);
12748   int val;
12749   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12750     (*p) += len + 1;
12751     sscanf(*p, "%d", &val);
12752     *loc = (val != 0);
12753     while (**p && **p != ' ') (*p)++;
12754     sprintf(buf, "accepted %s\n", name);
12755     SendToProgram(buf, cps);
12756     return TRUE;
12757   }
12758   return FALSE;
12759 }
12760
12761 int
12762 IntFeature(p, name, loc, cps)
12763      char **p;
12764      char *name;
12765      int *loc;
12766      ChessProgramState *cps;
12767 {
12768   char buf[MSG_SIZ];
12769   int len = strlen(name);
12770   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12771     (*p) += len + 1;
12772     sscanf(*p, "%d", loc);
12773     while (**p && **p != ' ') (*p)++;
12774     sprintf(buf, "accepted %s\n", name);
12775     SendToProgram(buf, cps);
12776     return TRUE;
12777   }
12778   return FALSE;
12779 }
12780
12781 int
12782 StringFeature(p, name, loc, cps)
12783      char **p;
12784      char *name;
12785      char loc[];
12786      ChessProgramState *cps;
12787 {
12788   char buf[MSG_SIZ];
12789   int len = strlen(name);
12790   if (strncmp((*p), name, len) == 0
12791       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12792     (*p) += len + 2;
12793     sscanf(*p, "%[^\"]", loc);
12794     while (**p && **p != '\"') (*p)++;
12795     if (**p == '\"') (*p)++;
12796     sprintf(buf, "accepted %s\n", name);
12797     SendToProgram(buf, cps);
12798     return TRUE;
12799   }
12800   return FALSE;
12801 }
12802
12803 int 
12804 ParseOption(Option *opt, ChessProgramState *cps)
12805 // [HGM] options: process the string that defines an engine option, and determine
12806 // name, type, default value, and allowed value range
12807 {
12808         char *p, *q, buf[MSG_SIZ];
12809         int n, min = (-1)<<31, max = 1<<31, def;
12810
12811         if(p = strstr(opt->name, " -spin ")) {
12812             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12813             if(max < min) max = min; // enforce consistency
12814             if(def < min) def = min;
12815             if(def > max) def = max;
12816             opt->value = def;
12817             opt->min = min;
12818             opt->max = max;
12819             opt->type = Spin;
12820         } else if((p = strstr(opt->name, " -slider "))) {
12821             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12822             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12823             if(max < min) max = min; // enforce consistency
12824             if(def < min) def = min;
12825             if(def > max) def = max;
12826             opt->value = def;
12827             opt->min = min;
12828             opt->max = max;
12829             opt->type = Spin; // Slider;
12830         } else if((p = strstr(opt->name, " -string "))) {
12831             opt->textValue = p+9;
12832             opt->type = TextBox;
12833         } else if((p = strstr(opt->name, " -file "))) {
12834             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12835             opt->textValue = p+7;
12836             opt->type = TextBox; // FileName;
12837         } else if((p = strstr(opt->name, " -path "))) {
12838             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12839             opt->textValue = p+7;
12840             opt->type = TextBox; // PathName;
12841         } else if(p = strstr(opt->name, " -check ")) {
12842             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12843             opt->value = (def != 0);
12844             opt->type = CheckBox;
12845         } else if(p = strstr(opt->name, " -combo ")) {
12846             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12847             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12848             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12849             opt->value = n = 0;
12850             while(q = StrStr(q, " /// ")) {
12851                 n++; *q = 0;    // count choices, and null-terminate each of them
12852                 q += 5;
12853                 if(*q == '*') { // remember default, which is marked with * prefix
12854                     q++;
12855                     opt->value = n;
12856                 }
12857                 cps->comboList[cps->comboCnt++] = q;
12858             }
12859             cps->comboList[cps->comboCnt++] = NULL;
12860             opt->max = n + 1;
12861             opt->type = ComboBox;
12862         } else if(p = strstr(opt->name, " -button")) {
12863             opt->type = Button;
12864         } else if(p = strstr(opt->name, " -save")) {
12865             opt->type = SaveButton;
12866         } else return FALSE;
12867         *p = 0; // terminate option name
12868         // now look if the command-line options define a setting for this engine option.
12869         if(cps->optionSettings && cps->optionSettings[0])
12870             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12871         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12872                 sprintf(buf, "option %s", p);
12873                 if(p = strstr(buf, ",")) *p = 0;
12874                 strcat(buf, "\n");
12875                 SendToProgram(buf, cps);
12876         }
12877         return TRUE;
12878 }
12879
12880 void
12881 FeatureDone(cps, val)
12882      ChessProgramState* cps;
12883      int val;
12884 {
12885   DelayedEventCallback cb = GetDelayedEvent();
12886   if ((cb == InitBackEnd3 && cps == &first) ||
12887       (cb == TwoMachinesEventIfReady && cps == &second)) {
12888     CancelDelayedEvent();
12889     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12890   }
12891   cps->initDone = val;
12892 }
12893
12894 /* Parse feature command from engine */
12895 void
12896 ParseFeatures(args, cps)
12897      char* args;
12898      ChessProgramState *cps;  
12899 {
12900   char *p = args;
12901   char *q;
12902   int val;
12903   char buf[MSG_SIZ];
12904
12905   for (;;) {
12906     while (*p == ' ') p++;
12907     if (*p == NULLCHAR) return;
12908
12909     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12910     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12911     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12912     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12913     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12914     if (BoolFeature(&p, "reuse", &val, cps)) {
12915       /* Engine can disable reuse, but can't enable it if user said no */
12916       if (!val) cps->reuse = FALSE;
12917       continue;
12918     }
12919     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12920     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12921       if (gameMode == TwoMachinesPlay) {
12922         DisplayTwoMachinesTitle();
12923       } else {
12924         DisplayTitle("");
12925       }
12926       continue;
12927     }
12928     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12929     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12930     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12931     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12932     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12933     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12934     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12935     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12936     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12937     if (IntFeature(&p, "done", &val, cps)) {
12938       FeatureDone(cps, val);
12939       continue;
12940     }
12941     /* Added by Tord: */
12942     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12943     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12944     /* End of additions by Tord */
12945
12946     /* [HGM] added features: */
12947     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12948     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12949     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12950     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12951     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12952     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12953     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12954         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12955             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12956             SendToProgram(buf, cps);
12957             continue;
12958         }
12959         if(cps->nrOptions >= MAX_OPTIONS) {
12960             cps->nrOptions--;
12961             sprintf(buf, "%s engine has too many options\n", cps->which);
12962             DisplayError(buf, 0);
12963         }
12964         continue;
12965     }
12966     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12967     /* End of additions by HGM */
12968
12969     /* unknown feature: complain and skip */
12970     q = p;
12971     while (*q && *q != '=') q++;
12972     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12973     SendToProgram(buf, cps);
12974     p = q;
12975     if (*p == '=') {
12976       p++;
12977       if (*p == '\"') {
12978         p++;
12979         while (*p && *p != '\"') p++;
12980         if (*p == '\"') p++;
12981       } else {
12982         while (*p && *p != ' ') p++;
12983       }
12984     }
12985   }
12986
12987 }
12988
12989 void
12990 PeriodicUpdatesEvent(newState)
12991      int newState;
12992 {
12993     if (newState == appData.periodicUpdates)
12994       return;
12995
12996     appData.periodicUpdates=newState;
12997
12998     /* Display type changes, so update it now */
12999 //    DisplayAnalysis();
13000
13001     /* Get the ball rolling again... */
13002     if (newState) {
13003         AnalysisPeriodicEvent(1);
13004         StartAnalysisClock();
13005     }
13006 }
13007
13008 void
13009 PonderNextMoveEvent(newState)
13010      int newState;
13011 {
13012     if (newState == appData.ponderNextMove) return;
13013     if (gameMode == EditPosition) EditPositionDone(TRUE);
13014     if (newState) {
13015         SendToProgram("hard\n", &first);
13016         if (gameMode == TwoMachinesPlay) {
13017             SendToProgram("hard\n", &second);
13018         }
13019     } else {
13020         SendToProgram("easy\n", &first);
13021         thinkOutput[0] = NULLCHAR;
13022         if (gameMode == TwoMachinesPlay) {
13023             SendToProgram("easy\n", &second);
13024         }
13025     }
13026     appData.ponderNextMove = newState;
13027 }
13028
13029 void
13030 NewSettingEvent(option, command, value)
13031      char *command;
13032      int option, value;
13033 {
13034     char buf[MSG_SIZ];
13035
13036     if (gameMode == EditPosition) EditPositionDone(TRUE);
13037     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13038     SendToProgram(buf, &first);
13039     if (gameMode == TwoMachinesPlay) {
13040         SendToProgram(buf, &second);
13041     }
13042 }
13043
13044 void
13045 ShowThinkingEvent()
13046 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13047 {
13048     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13049     int newState = appData.showThinking
13050         // [HGM] thinking: other features now need thinking output as well
13051         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13052     
13053     if (oldState == newState) return;
13054     oldState = newState;
13055     if (gameMode == EditPosition) EditPositionDone(TRUE);
13056     if (oldState) {
13057         SendToProgram("post\n", &first);
13058         if (gameMode == TwoMachinesPlay) {
13059             SendToProgram("post\n", &second);
13060         }
13061     } else {
13062         SendToProgram("nopost\n", &first);
13063         thinkOutput[0] = NULLCHAR;
13064         if (gameMode == TwoMachinesPlay) {
13065             SendToProgram("nopost\n", &second);
13066         }
13067     }
13068 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13069 }
13070
13071 void
13072 AskQuestionEvent(title, question, replyPrefix, which)
13073      char *title; char *question; char *replyPrefix; char *which;
13074 {
13075   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13076   if (pr == NoProc) return;
13077   AskQuestion(title, question, replyPrefix, pr);
13078 }
13079
13080 void
13081 DisplayMove(moveNumber)
13082      int moveNumber;
13083 {
13084     char message[MSG_SIZ];
13085     char res[MSG_SIZ];
13086     char cpThinkOutput[MSG_SIZ];
13087
13088     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13089     
13090     if (moveNumber == forwardMostMove - 1 || 
13091         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13092
13093         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13094
13095         if (strchr(cpThinkOutput, '\n')) {
13096             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13097         }
13098     } else {
13099         *cpThinkOutput = NULLCHAR;
13100     }
13101
13102     /* [AS] Hide thinking from human user */
13103     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13104         *cpThinkOutput = NULLCHAR;
13105         if( thinkOutput[0] != NULLCHAR ) {
13106             int i;
13107
13108             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13109                 cpThinkOutput[i] = '.';
13110             }
13111             cpThinkOutput[i] = NULLCHAR;
13112             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13113         }
13114     }
13115
13116     if (moveNumber == forwardMostMove - 1 &&
13117         gameInfo.resultDetails != NULL) {
13118         if (gameInfo.resultDetails[0] == NULLCHAR) {
13119             sprintf(res, " %s", PGNResult(gameInfo.result));
13120         } else {
13121             sprintf(res, " {%s} %s",
13122                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13123         }
13124     } else {
13125         res[0] = NULLCHAR;
13126     }
13127
13128     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13129         DisplayMessage(res, cpThinkOutput);
13130     } else {
13131         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13132                 WhiteOnMove(moveNumber) ? " " : ".. ",
13133                 parseList[moveNumber], res);
13134         DisplayMessage(message, cpThinkOutput);
13135     }
13136 }
13137
13138 void
13139 DisplayComment(moveNumber, text)
13140      int moveNumber;
13141      char *text;
13142 {
13143     char title[MSG_SIZ];
13144     char buf[8000]; // comment can be long!
13145     int score, depth;
13146     
13147     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13148       strcpy(title, "Comment");
13149     } else {
13150       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13151               WhiteOnMove(moveNumber) ? " " : ".. ",
13152               parseList[moveNumber]);
13153     }
13154     // [HGM] PV info: display PV info together with (or as) comment
13155     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13156       if(text == NULL) text = "";                                           
13157       score = pvInfoList[moveNumber].score;
13158       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13159               depth, (pvInfoList[moveNumber].time+50)/100, text);
13160       text = buf;
13161     }
13162     if (text != NULL && (appData.autoDisplayComment || commentUp))
13163         CommentPopUp(title, text);
13164 }
13165
13166 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13167  * might be busy thinking or pondering.  It can be omitted if your
13168  * gnuchess is configured to stop thinking immediately on any user
13169  * input.  However, that gnuchess feature depends on the FIONREAD
13170  * ioctl, which does not work properly on some flavors of Unix.
13171  */
13172 void
13173 Attention(cps)
13174      ChessProgramState *cps;
13175 {
13176 #if ATTENTION
13177     if (!cps->useSigint) return;
13178     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13179     switch (gameMode) {
13180       case MachinePlaysWhite:
13181       case MachinePlaysBlack:
13182       case TwoMachinesPlay:
13183       case IcsPlayingWhite:
13184       case IcsPlayingBlack:
13185       case AnalyzeMode:
13186       case AnalyzeFile:
13187         /* Skip if we know it isn't thinking */
13188         if (!cps->maybeThinking) return;
13189         if (appData.debugMode)
13190           fprintf(debugFP, "Interrupting %s\n", cps->which);
13191         InterruptChildProcess(cps->pr);
13192         cps->maybeThinking = FALSE;
13193         break;
13194       default:
13195         break;
13196     }
13197 #endif /*ATTENTION*/
13198 }
13199
13200 int
13201 CheckFlags()
13202 {
13203     if (whiteTimeRemaining <= 0) {
13204         if (!whiteFlag) {
13205             whiteFlag = TRUE;
13206             if (appData.icsActive) {
13207                 if (appData.autoCallFlag &&
13208                     gameMode == IcsPlayingBlack && !blackFlag) {
13209                   SendToICS(ics_prefix);
13210                   SendToICS("flag\n");
13211                 }
13212             } else {
13213                 if (blackFlag) {
13214                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13215                 } else {
13216                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13217                     if (appData.autoCallFlag) {
13218                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13219                         return TRUE;
13220                     }
13221                 }
13222             }
13223         }
13224     }
13225     if (blackTimeRemaining <= 0) {
13226         if (!blackFlag) {
13227             blackFlag = TRUE;
13228             if (appData.icsActive) {
13229                 if (appData.autoCallFlag &&
13230                     gameMode == IcsPlayingWhite && !whiteFlag) {
13231                   SendToICS(ics_prefix);
13232                   SendToICS("flag\n");
13233                 }
13234             } else {
13235                 if (whiteFlag) {
13236                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13237                 } else {
13238                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13239                     if (appData.autoCallFlag) {
13240                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13241                         return TRUE;
13242                     }
13243                 }
13244             }
13245         }
13246     }
13247     return FALSE;
13248 }
13249
13250 void
13251 CheckTimeControl()
13252 {
13253     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13254         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13255
13256     /*
13257      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13258      */
13259     if ( !WhiteOnMove(forwardMostMove) )
13260         /* White made time control */
13261         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13262         /* [HGM] time odds: correct new time quota for time odds! */
13263                                             / WhitePlayer()->timeOdds;
13264       else
13265         /* Black made time control */
13266         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13267                                             / WhitePlayer()->other->timeOdds;
13268 }
13269
13270 void
13271 DisplayBothClocks()
13272 {
13273     int wom = gameMode == EditPosition ?
13274       !blackPlaysFirst : WhiteOnMove(currentMove);
13275     DisplayWhiteClock(whiteTimeRemaining, wom);
13276     DisplayBlackClock(blackTimeRemaining, !wom);
13277 }
13278
13279
13280 /* Timekeeping seems to be a portability nightmare.  I think everyone
13281    has ftime(), but I'm really not sure, so I'm including some ifdefs
13282    to use other calls if you don't.  Clocks will be less accurate if
13283    you have neither ftime nor gettimeofday.
13284 */
13285
13286 /* VS 2008 requires the #include outside of the function */
13287 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13288 #include <sys/timeb.h>
13289 #endif
13290
13291 /* Get the current time as a TimeMark */
13292 void
13293 GetTimeMark(tm)
13294      TimeMark *tm;
13295 {
13296 #if HAVE_GETTIMEOFDAY
13297
13298     struct timeval timeVal;
13299     struct timezone timeZone;
13300
13301     gettimeofday(&timeVal, &timeZone);
13302     tm->sec = (long) timeVal.tv_sec; 
13303     tm->ms = (int) (timeVal.tv_usec / 1000L);
13304
13305 #else /*!HAVE_GETTIMEOFDAY*/
13306 #if HAVE_FTIME
13307
13308 // include <sys/timeb.h> / moved to just above start of function
13309     struct timeb timeB;
13310
13311     ftime(&timeB);
13312     tm->sec = (long) timeB.time;
13313     tm->ms = (int) timeB.millitm;
13314
13315 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13316     tm->sec = (long) time(NULL);
13317     tm->ms = 0;
13318 #endif
13319 #endif
13320 }
13321
13322 /* Return the difference in milliseconds between two
13323    time marks.  We assume the difference will fit in a long!
13324 */
13325 long
13326 SubtractTimeMarks(tm2, tm1)
13327      TimeMark *tm2, *tm1;
13328 {
13329     return 1000L*(tm2->sec - tm1->sec) +
13330            (long) (tm2->ms - tm1->ms);
13331 }
13332
13333
13334 /*
13335  * Code to manage the game clocks.
13336  *
13337  * In tournament play, black starts the clock and then white makes a move.
13338  * We give the human user a slight advantage if he is playing white---the
13339  * clocks don't run until he makes his first move, so it takes zero time.
13340  * Also, we don't account for network lag, so we could get out of sync
13341  * with GNU Chess's clock -- but then, referees are always right.  
13342  */
13343
13344 static TimeMark tickStartTM;
13345 static long intendedTickLength;
13346
13347 long
13348 NextTickLength(timeRemaining)
13349      long timeRemaining;
13350 {
13351     long nominalTickLength, nextTickLength;
13352
13353     if (timeRemaining > 0L && timeRemaining <= 10000L)
13354       nominalTickLength = 100L;
13355     else
13356       nominalTickLength = 1000L;
13357     nextTickLength = timeRemaining % nominalTickLength;
13358     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13359
13360     return nextTickLength;
13361 }
13362
13363 /* Adjust clock one minute up or down */
13364 void
13365 AdjustClock(Boolean which, int dir)
13366 {
13367     if(which) blackTimeRemaining += 60000*dir;
13368     else      whiteTimeRemaining += 60000*dir;
13369     DisplayBothClocks();
13370 }
13371
13372 /* Stop clocks and reset to a fresh time control */
13373 void
13374 ResetClocks() 
13375 {
13376     (void) StopClockTimer();
13377     if (appData.icsActive) {
13378         whiteTimeRemaining = blackTimeRemaining = 0;
13379     } else if (searchTime) {
13380         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13381         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13382     } else { /* [HGM] correct new time quote for time odds */
13383         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13384         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13385     }
13386     if (whiteFlag || blackFlag) {
13387         DisplayTitle("");
13388         whiteFlag = blackFlag = FALSE;
13389     }
13390     DisplayBothClocks();
13391 }
13392
13393 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13394
13395 /* Decrement running clock by amount of time that has passed */
13396 void
13397 DecrementClocks()
13398 {
13399     long timeRemaining;
13400     long lastTickLength, fudge;
13401     TimeMark now;
13402
13403     if (!appData.clockMode) return;
13404     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13405         
13406     GetTimeMark(&now);
13407
13408     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13409
13410     /* Fudge if we woke up a little too soon */
13411     fudge = intendedTickLength - lastTickLength;
13412     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13413
13414     if (WhiteOnMove(forwardMostMove)) {
13415         if(whiteNPS >= 0) lastTickLength = 0;
13416         timeRemaining = whiteTimeRemaining -= lastTickLength;
13417         DisplayWhiteClock(whiteTimeRemaining - fudge,
13418                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13419     } else {
13420         if(blackNPS >= 0) lastTickLength = 0;
13421         timeRemaining = blackTimeRemaining -= lastTickLength;
13422         DisplayBlackClock(blackTimeRemaining - fudge,
13423                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13424     }
13425
13426     if (CheckFlags()) return;
13427         
13428     tickStartTM = now;
13429     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13430     StartClockTimer(intendedTickLength);
13431
13432     /* if the time remaining has fallen below the alarm threshold, sound the
13433      * alarm. if the alarm has sounded and (due to a takeback or time control
13434      * with increment) the time remaining has increased to a level above the
13435      * threshold, reset the alarm so it can sound again. 
13436      */
13437     
13438     if (appData.icsActive && appData.icsAlarm) {
13439
13440         /* make sure we are dealing with the user's clock */
13441         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13442                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13443            )) return;
13444
13445         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13446             alarmSounded = FALSE;
13447         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13448             PlayAlarmSound();
13449             alarmSounded = TRUE;
13450         }
13451     }
13452 }
13453
13454
13455 /* A player has just moved, so stop the previously running
13456    clock and (if in clock mode) start the other one.
13457    We redisplay both clocks in case we're in ICS mode, because
13458    ICS gives us an update to both clocks after every move.
13459    Note that this routine is called *after* forwardMostMove
13460    is updated, so the last fractional tick must be subtracted
13461    from the color that is *not* on move now.
13462 */
13463 void
13464 SwitchClocks()
13465 {
13466     long lastTickLength;
13467     TimeMark now;
13468     int flagged = FALSE;
13469
13470     GetTimeMark(&now);
13471
13472     if (StopClockTimer() && appData.clockMode) {
13473         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13474         if (WhiteOnMove(forwardMostMove)) {
13475             if(blackNPS >= 0) lastTickLength = 0;
13476             blackTimeRemaining -= lastTickLength;
13477            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13478 //         if(pvInfoList[forwardMostMove-1].time == -1)
13479                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13480                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13481         } else {
13482            if(whiteNPS >= 0) lastTickLength = 0;
13483            whiteTimeRemaining -= lastTickLength;
13484            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13485 //         if(pvInfoList[forwardMostMove-1].time == -1)
13486                  pvInfoList[forwardMostMove-1].time = 
13487                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13488         }
13489         flagged = CheckFlags();
13490     }
13491     CheckTimeControl();
13492
13493     if (flagged || !appData.clockMode) return;
13494
13495     switch (gameMode) {
13496       case MachinePlaysBlack:
13497       case MachinePlaysWhite:
13498       case BeginningOfGame:
13499         if (pausing) return;
13500         break;
13501
13502       case EditGame:
13503       case PlayFromGameFile:
13504       case IcsExamining:
13505         return;
13506
13507       default:
13508         break;
13509     }
13510
13511     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13512         if(WhiteOnMove(forwardMostMove))
13513              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13514         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13515     }
13516
13517     tickStartTM = now;
13518     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13519       whiteTimeRemaining : blackTimeRemaining);
13520     StartClockTimer(intendedTickLength);
13521 }
13522         
13523
13524 /* Stop both clocks */
13525 void
13526 StopClocks()
13527 {       
13528     long lastTickLength;
13529     TimeMark now;
13530
13531     if (!StopClockTimer()) return;
13532     if (!appData.clockMode) return;
13533
13534     GetTimeMark(&now);
13535
13536     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13537     if (WhiteOnMove(forwardMostMove)) {
13538         if(whiteNPS >= 0) lastTickLength = 0;
13539         whiteTimeRemaining -= lastTickLength;
13540         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13541     } else {
13542         if(blackNPS >= 0) lastTickLength = 0;
13543         blackTimeRemaining -= lastTickLength;
13544         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13545     }
13546     CheckFlags();
13547 }
13548         
13549 /* Start clock of player on move.  Time may have been reset, so
13550    if clock is already running, stop and restart it. */
13551 void
13552 StartClocks()
13553 {
13554     (void) StopClockTimer(); /* in case it was running already */
13555     DisplayBothClocks();
13556     if (CheckFlags()) return;
13557
13558     if (!appData.clockMode) return;
13559     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13560
13561     GetTimeMark(&tickStartTM);
13562     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13563       whiteTimeRemaining : blackTimeRemaining);
13564
13565    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13566     whiteNPS = blackNPS = -1; 
13567     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13568        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13569         whiteNPS = first.nps;
13570     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13571        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13572         blackNPS = first.nps;
13573     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13574         whiteNPS = second.nps;
13575     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13576         blackNPS = second.nps;
13577     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13578
13579     StartClockTimer(intendedTickLength);
13580 }
13581
13582 char *
13583 TimeString(ms)
13584      long ms;
13585 {
13586     long second, minute, hour, day;
13587     char *sign = "";
13588     static char buf[32];
13589     
13590     if (ms > 0 && ms <= 9900) {
13591       /* convert milliseconds to tenths, rounding up */
13592       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13593
13594       sprintf(buf, " %03.1f ", tenths/10.0);
13595       return buf;
13596     }
13597
13598     /* convert milliseconds to seconds, rounding up */
13599     /* use floating point to avoid strangeness of integer division
13600        with negative dividends on many machines */
13601     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13602
13603     if (second < 0) {
13604         sign = "-";
13605         second = -second;
13606     }
13607     
13608     day = second / (60 * 60 * 24);
13609     second = second % (60 * 60 * 24);
13610     hour = second / (60 * 60);
13611     second = second % (60 * 60);
13612     minute = second / 60;
13613     second = second % 60;
13614     
13615     if (day > 0)
13616       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13617               sign, day, hour, minute, second);
13618     else if (hour > 0)
13619       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13620     else
13621       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13622     
13623     return buf;
13624 }
13625
13626
13627 /*
13628  * This is necessary because some C libraries aren't ANSI C compliant yet.
13629  */
13630 char *
13631 StrStr(string, match)
13632      char *string, *match;
13633 {
13634     int i, length;
13635     
13636     length = strlen(match);
13637     
13638     for (i = strlen(string) - length; i >= 0; i--, string++)
13639       if (!strncmp(match, string, length))
13640         return string;
13641     
13642     return NULL;
13643 }
13644
13645 char *
13646 StrCaseStr(string, match)
13647      char *string, *match;
13648 {
13649     int i, j, length;
13650     
13651     length = strlen(match);
13652     
13653     for (i = strlen(string) - length; i >= 0; i--, string++) {
13654         for (j = 0; j < length; j++) {
13655             if (ToLower(match[j]) != ToLower(string[j]))
13656               break;
13657         }
13658         if (j == length) return string;
13659     }
13660
13661     return NULL;
13662 }
13663
13664 #ifndef _amigados
13665 int
13666 StrCaseCmp(s1, s2)
13667      char *s1, *s2;
13668 {
13669     char c1, c2;
13670     
13671     for (;;) {
13672         c1 = ToLower(*s1++);
13673         c2 = ToLower(*s2++);
13674         if (c1 > c2) return 1;
13675         if (c1 < c2) return -1;
13676         if (c1 == NULLCHAR) return 0;
13677     }
13678 }
13679
13680
13681 int
13682 ToLower(c)
13683      int c;
13684 {
13685     return isupper(c) ? tolower(c) : c;
13686 }
13687
13688
13689 int
13690 ToUpper(c)
13691      int c;
13692 {
13693     return islower(c) ? toupper(c) : c;
13694 }
13695 #endif /* !_amigados    */
13696
13697 char *
13698 StrSave(s)
13699      char *s;
13700 {
13701     char *ret;
13702
13703     if ((ret = (char *) malloc(strlen(s) + 1))) {
13704         strcpy(ret, s);
13705     }
13706     return ret;
13707 }
13708
13709 char *
13710 StrSavePtr(s, savePtr)
13711      char *s, **savePtr;
13712 {
13713     if (*savePtr) {
13714         free(*savePtr);
13715     }
13716     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13717         strcpy(*savePtr, s);
13718     }
13719     return(*savePtr);
13720 }
13721
13722 char *
13723 PGNDate()
13724 {
13725     time_t clock;
13726     struct tm *tm;
13727     char buf[MSG_SIZ];
13728
13729     clock = time((time_t *)NULL);
13730     tm = localtime(&clock);
13731     sprintf(buf, "%04d.%02d.%02d",
13732             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13733     return StrSave(buf);
13734 }
13735
13736
13737 char *
13738 PositionToFEN(move, overrideCastling)
13739      int move;
13740      char *overrideCastling;
13741 {
13742     int i, j, fromX, fromY, toX, toY;
13743     int whiteToPlay;
13744     char buf[128];
13745     char *p, *q;
13746     int emptycount;
13747     ChessSquare piece;
13748
13749     whiteToPlay = (gameMode == EditPosition) ?
13750       !blackPlaysFirst : (move % 2 == 0);
13751     p = buf;
13752
13753     /* Piece placement data */
13754     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13755         emptycount = 0;
13756         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13757             if (boards[move][i][j] == EmptySquare) {
13758                 emptycount++;
13759             } else { ChessSquare piece = boards[move][i][j];
13760                 if (emptycount > 0) {
13761                     if(emptycount<10) /* [HGM] can be >= 10 */
13762                         *p++ = '0' + emptycount;
13763                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13764                     emptycount = 0;
13765                 }
13766                 if(PieceToChar(piece) == '+') {
13767                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13768                     *p++ = '+';
13769                     piece = (ChessSquare)(DEMOTED piece);
13770                 } 
13771                 *p++ = PieceToChar(piece);
13772                 if(p[-1] == '~') {
13773                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13774                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13775                     *p++ = '~';
13776                 }
13777             }
13778         }
13779         if (emptycount > 0) {
13780             if(emptycount<10) /* [HGM] can be >= 10 */
13781                 *p++ = '0' + emptycount;
13782             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13783             emptycount = 0;
13784         }
13785         *p++ = '/';
13786     }
13787     *(p - 1) = ' ';
13788
13789     /* [HGM] print Crazyhouse or Shogi holdings */
13790     if( gameInfo.holdingsWidth ) {
13791         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13792         q = p;
13793         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13794             piece = boards[move][i][BOARD_WIDTH-1];
13795             if( piece != EmptySquare )
13796               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13797                   *p++ = PieceToChar(piece);
13798         }
13799         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13800             piece = boards[move][BOARD_HEIGHT-i-1][0];
13801             if( piece != EmptySquare )
13802               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13803                   *p++ = PieceToChar(piece);
13804         }
13805
13806         if( q == p ) *p++ = '-';
13807         *p++ = ']';
13808         *p++ = ' ';
13809     }
13810
13811     /* Active color */
13812     *p++ = whiteToPlay ? 'w' : 'b';
13813     *p++ = ' ';
13814
13815   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13816     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13817   } else {
13818   if(nrCastlingRights) {
13819      q = p;
13820      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13821        /* [HGM] write directly from rights */
13822            if(boards[move][CASTLING][2] != NoRights &&
13823               boards[move][CASTLING][0] != NoRights   )
13824                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13825            if(boards[move][CASTLING][2] != NoRights &&
13826               boards[move][CASTLING][1] != NoRights   )
13827                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13828            if(boards[move][CASTLING][5] != NoRights &&
13829               boards[move][CASTLING][3] != NoRights   )
13830                 *p++ = boards[move][CASTLING][3] + AAA;
13831            if(boards[move][CASTLING][5] != NoRights &&
13832               boards[move][CASTLING][4] != NoRights   )
13833                 *p++ = boards[move][CASTLING][4] + AAA;
13834      } else {
13835
13836         /* [HGM] write true castling rights */
13837         if( nrCastlingRights == 6 ) {
13838             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13839                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13840             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13841                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13842             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13843                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13844             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13845                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13846         }
13847      }
13848      if (q == p) *p++ = '-'; /* No castling rights */
13849      *p++ = ' ';
13850   }
13851
13852   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13853      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13854     /* En passant target square */
13855     if (move > backwardMostMove) {
13856         fromX = moveList[move - 1][0] - AAA;
13857         fromY = moveList[move - 1][1] - ONE;
13858         toX = moveList[move - 1][2] - AAA;
13859         toY = moveList[move - 1][3] - ONE;
13860         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13861             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13862             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13863             fromX == toX) {
13864             /* 2-square pawn move just happened */
13865             *p++ = toX + AAA;
13866             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13867         } else {
13868             *p++ = '-';
13869         }
13870     } else if(move == backwardMostMove) {
13871         // [HGM] perhaps we should always do it like this, and forget the above?
13872         if((signed char)boards[move][EP_STATUS] >= 0) {
13873             *p++ = boards[move][EP_STATUS] + AAA;
13874             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13875         } else {
13876             *p++ = '-';
13877         }
13878     } else {
13879         *p++ = '-';
13880     }
13881     *p++ = ' ';
13882   }
13883   }
13884
13885     /* [HGM] find reversible plies */
13886     {   int i = 0, j=move;
13887
13888         if (appData.debugMode) { int k;
13889             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13890             for(k=backwardMostMove; k<=forwardMostMove; k++)
13891                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13892
13893         }
13894
13895         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13896         if( j == backwardMostMove ) i += initialRulePlies;
13897         sprintf(p, "%d ", i);
13898         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13899     }
13900     /* Fullmove number */
13901     sprintf(p, "%d", (move / 2) + 1);
13902     
13903     return StrSave(buf);
13904 }
13905
13906 Boolean
13907 ParseFEN(board, blackPlaysFirst, fen)
13908     Board board;
13909      int *blackPlaysFirst;
13910      char *fen;
13911 {
13912     int i, j;
13913     char *p;
13914     int emptycount;
13915     ChessSquare piece;
13916
13917     p = fen;
13918
13919     /* [HGM] by default clear Crazyhouse holdings, if present */
13920     if(gameInfo.holdingsWidth) {
13921        for(i=0; i<BOARD_HEIGHT; i++) {
13922            board[i][0]             = EmptySquare; /* black holdings */
13923            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13924            board[i][1]             = (ChessSquare) 0; /* black counts */
13925            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13926        }
13927     }
13928
13929     /* Piece placement data */
13930     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13931         j = 0;
13932         for (;;) {
13933             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13934                 if (*p == '/') p++;
13935                 emptycount = gameInfo.boardWidth - j;
13936                 while (emptycount--)
13937                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13938                 break;
13939 #if(BOARD_FILES >= 10)
13940             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13941                 p++; emptycount=10;
13942                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13943                 while (emptycount--)
13944                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13945 #endif
13946             } else if (isdigit(*p)) {
13947                 emptycount = *p++ - '0';
13948                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13949                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13950                 while (emptycount--)
13951                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13952             } else if (*p == '+' || isalpha(*p)) {
13953                 if (j >= gameInfo.boardWidth) return FALSE;
13954                 if(*p=='+') {
13955                     piece = CharToPiece(*++p);
13956                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13957                     piece = (ChessSquare) (PROMOTED piece ); p++;
13958                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13959                 } else piece = CharToPiece(*p++);
13960
13961                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13962                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13963                     piece = (ChessSquare) (PROMOTED piece);
13964                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13965                     p++;
13966                 }
13967                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13968             } else {
13969                 return FALSE;
13970             }
13971         }
13972     }
13973     while (*p == '/' || *p == ' ') p++;
13974
13975     /* [HGM] look for Crazyhouse holdings here */
13976     while(*p==' ') p++;
13977     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13978         if(*p == '[') p++;
13979         if(*p == '-' ) *p++; /* empty holdings */ else {
13980             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13981             /* if we would allow FEN reading to set board size, we would   */
13982             /* have to add holdings and shift the board read so far here   */
13983             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13984                 *p++;
13985                 if((int) piece >= (int) BlackPawn ) {
13986                     i = (int)piece - (int)BlackPawn;
13987                     i = PieceToNumber((ChessSquare)i);
13988                     if( i >= gameInfo.holdingsSize ) return FALSE;
13989                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13990                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13991                 } else {
13992                     i = (int)piece - (int)WhitePawn;
13993                     i = PieceToNumber((ChessSquare)i);
13994                     if( i >= gameInfo.holdingsSize ) return FALSE;
13995                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13996                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13997                 }
13998             }
13999         }
14000         if(*p == ']') *p++;
14001     }
14002
14003     while(*p == ' ') p++;
14004
14005     /* Active color */
14006     switch (*p++) {
14007       case 'w':
14008         *blackPlaysFirst = FALSE;
14009         break;
14010       case 'b': 
14011         *blackPlaysFirst = TRUE;
14012         break;
14013       default:
14014         return FALSE;
14015     }
14016
14017     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14018     /* return the extra info in global variiables             */
14019
14020     /* set defaults in case FEN is incomplete */
14021     board[EP_STATUS] = EP_UNKNOWN;
14022     for(i=0; i<nrCastlingRights; i++ ) {
14023         board[CASTLING][i] =
14024             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14025     }   /* assume possible unless obviously impossible */
14026     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14027     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14028     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14029     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14030     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14031     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14032     FENrulePlies = 0;
14033
14034     while(*p==' ') p++;
14035     if(nrCastlingRights) {
14036       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14037           /* castling indicator present, so default becomes no castlings */
14038           for(i=0; i<nrCastlingRights; i++ ) {
14039                  board[CASTLING][i] = NoRights;
14040           }
14041       }
14042       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14043              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14044              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14045              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14046         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14047
14048         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14049             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14050             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14051         }
14052         switch(c) {
14053           case'K':
14054               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14055               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14056               board[CASTLING][2] = whiteKingFile;
14057               break;
14058           case'Q':
14059               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14060               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14061               board[CASTLING][2] = whiteKingFile;
14062               break;
14063           case'k':
14064               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14065               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14066               board[CASTLING][5] = blackKingFile;
14067               break;
14068           case'q':
14069               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14070               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14071               board[CASTLING][5] = blackKingFile;
14072           case '-':
14073               break;
14074           default: /* FRC castlings */
14075               if(c >= 'a') { /* black rights */
14076                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14077                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14078                   if(i == BOARD_RGHT) break;
14079                   board[CASTLING][5] = i;
14080                   c -= AAA;
14081                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14082                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14083                   if(c > i)
14084                       board[CASTLING][3] = c;
14085                   else
14086                       board[CASTLING][4] = c;
14087               } else { /* white rights */
14088                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14089                     if(board[0][i] == WhiteKing) break;
14090                   if(i == BOARD_RGHT) break;
14091                   board[CASTLING][2] = i;
14092                   c -= AAA - 'a' + 'A';
14093                   if(board[0][c] >= WhiteKing) break;
14094                   if(c > i)
14095                       board[CASTLING][0] = c;
14096                   else
14097                       board[CASTLING][1] = c;
14098               }
14099         }
14100       }
14101     if (appData.debugMode) {
14102         fprintf(debugFP, "FEN castling rights:");
14103         for(i=0; i<nrCastlingRights; i++)
14104         fprintf(debugFP, " %d", board[CASTLING][i]);
14105         fprintf(debugFP, "\n");
14106     }
14107
14108       while(*p==' ') p++;
14109     }
14110
14111     /* read e.p. field in games that know e.p. capture */
14112     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14113        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14114       if(*p=='-') {
14115         p++; board[EP_STATUS] = EP_NONE;
14116       } else {
14117          char c = *p++ - AAA;
14118
14119          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14120          if(*p >= '0' && *p <='9') *p++;
14121          board[EP_STATUS] = c;
14122       }
14123     }
14124
14125
14126     if(sscanf(p, "%d", &i) == 1) {
14127         FENrulePlies = i; /* 50-move ply counter */
14128         /* (The move number is still ignored)    */
14129     }
14130
14131     return TRUE;
14132 }
14133       
14134 void
14135 EditPositionPasteFEN(char *fen)
14136 {
14137   if (fen != NULL) {
14138     Board initial_position;
14139
14140     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14141       DisplayError(_("Bad FEN position in clipboard"), 0);
14142       return ;
14143     } else {
14144       int savedBlackPlaysFirst = blackPlaysFirst;
14145       EditPositionEvent();
14146       blackPlaysFirst = savedBlackPlaysFirst;
14147       CopyBoard(boards[0], initial_position);
14148       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14149       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14150       DisplayBothClocks();
14151       DrawPosition(FALSE, boards[currentMove]);
14152     }
14153   }
14154 }
14155
14156 static char cseq[12] = "\\   ";
14157
14158 Boolean set_cont_sequence(char *new_seq)
14159 {
14160     int len;
14161     Boolean ret;
14162
14163     // handle bad attempts to set the sequence
14164         if (!new_seq)
14165                 return 0; // acceptable error - no debug
14166
14167     len = strlen(new_seq);
14168     ret = (len > 0) && (len < sizeof(cseq));
14169     if (ret)
14170         strcpy(cseq, new_seq);
14171     else if (appData.debugMode)
14172         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14173     return ret;
14174 }
14175
14176 /*
14177     reformat a source message so words don't cross the width boundary.  internal
14178     newlines are not removed.  returns the wrapped size (no null character unless
14179     included in source message).  If dest is NULL, only calculate the size required
14180     for the dest buffer.  lp argument indicats line position upon entry, and it's
14181     passed back upon exit.
14182 */
14183 int wrap(char *dest, char *src, int count, int width, int *lp)
14184 {
14185     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14186
14187     cseq_len = strlen(cseq);
14188     old_line = line = *lp;
14189     ansi = len = clen = 0;
14190
14191     for (i=0; i < count; i++)
14192     {
14193         if (src[i] == '\033')
14194             ansi = 1;
14195
14196         // if we hit the width, back up
14197         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14198         {
14199             // store i & len in case the word is too long
14200             old_i = i, old_len = len;
14201
14202             // find the end of the last word
14203             while (i && src[i] != ' ' && src[i] != '\n')
14204             {
14205                 i--;
14206                 len--;
14207             }
14208
14209             // word too long?  restore i & len before splitting it
14210             if ((old_i-i+clen) >= width)
14211             {
14212                 i = old_i;
14213                 len = old_len;
14214             }
14215
14216             // extra space?
14217             if (i && src[i-1] == ' ')
14218                 len--;
14219
14220             if (src[i] != ' ' && src[i] != '\n')
14221             {
14222                 i--;
14223                 if (len)
14224                     len--;
14225             }
14226
14227             // now append the newline and continuation sequence
14228             if (dest)
14229                 dest[len] = '\n';
14230             len++;
14231             if (dest)
14232                 strncpy(dest+len, cseq, cseq_len);
14233             len += cseq_len;
14234             line = cseq_len;
14235             clen = cseq_len;
14236             continue;
14237         }
14238
14239         if (dest)
14240             dest[len] = src[i];
14241         len++;
14242         if (!ansi)
14243             line++;
14244         if (src[i] == '\n')
14245             line = 0;
14246         if (src[i] == 'm')
14247             ansi = 0;
14248     }
14249     if (dest && appData.debugMode)
14250     {
14251         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14252             count, width, line, len, *lp);
14253         show_bytes(debugFP, src, count);
14254         fprintf(debugFP, "\ndest: ");
14255         show_bytes(debugFP, dest, len);
14256         fprintf(debugFP, "\n");
14257     }
14258     *lp = dest ? line : old_line;
14259
14260     return len;
14261 }
14262
14263 // [HGM] vari: routines for shelving variations
14264
14265 void 
14266 PushTail(int firstMove, int lastMove)
14267 {
14268         int i, j, nrMoves = lastMove - firstMove;
14269
14270         if(appData.icsActive) { // only in local mode
14271                 forwardMostMove = currentMove; // mimic old ICS behavior
14272                 return;
14273         }
14274         if(storedGames >= MAX_VARIATIONS-1) return;
14275
14276         // push current tail of game on stack
14277         savedResult[storedGames] = gameInfo.result;
14278         savedDetails[storedGames] = gameInfo.resultDetails;
14279         gameInfo.resultDetails = NULL;
14280         savedFirst[storedGames] = firstMove;
14281         savedLast [storedGames] = lastMove;
14282         savedFramePtr[storedGames] = framePtr;
14283         framePtr -= nrMoves; // reserve space for the boards
14284         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14285             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14286             for(j=0; j<MOVE_LEN; j++)
14287                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14288             for(j=0; j<2*MOVE_LEN; j++)
14289                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14290             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14291             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14292             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14293             pvInfoList[firstMove+i-1].depth = 0;
14294             commentList[framePtr+i] = commentList[firstMove+i];
14295             commentList[firstMove+i] = NULL;
14296         }
14297
14298         storedGames++;
14299         forwardMostMove = currentMove; // truncte game so we can start variation
14300         if(storedGames == 1) GreyRevert(FALSE);
14301 }
14302
14303 Boolean
14304 PopTail(Boolean annotate)
14305 {
14306         int i, j, nrMoves;
14307         char buf[8000], moveBuf[20];
14308
14309         if(appData.icsActive) return FALSE; // only in local mode
14310         if(!storedGames) return FALSE; // sanity
14311
14312         storedGames--;
14313         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14314         nrMoves = savedLast[storedGames] - currentMove;
14315         if(annotate) {
14316                 int cnt = 10;
14317                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14318                 else strcpy(buf, "(");
14319                 for(i=currentMove; i<forwardMostMove; i++) {
14320                         if(WhiteOnMove(i))
14321                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14322                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14323                         strcat(buf, moveBuf);
14324                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14325                 }
14326                 strcat(buf, ")");
14327         }
14328         for(i=1; i<nrMoves; i++) { // copy last variation back
14329             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14330             for(j=0; j<MOVE_LEN; j++)
14331                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14332             for(j=0; j<2*MOVE_LEN; j++)
14333                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14334             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14335             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14336             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14337             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14338             commentList[currentMove+i] = commentList[framePtr+i];
14339             commentList[framePtr+i] = NULL;
14340         }
14341         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14342         framePtr = savedFramePtr[storedGames];
14343         gameInfo.result = savedResult[storedGames];
14344         if(gameInfo.resultDetails != NULL) {
14345             free(gameInfo.resultDetails);
14346       }
14347         gameInfo.resultDetails = savedDetails[storedGames];
14348         forwardMostMove = currentMove + nrMoves;
14349         if(storedGames == 0) GreyRevert(TRUE);
14350         return TRUE;
14351 }
14352
14353 void 
14354 CleanupTail()
14355 {       // remove all shelved variations
14356         int i;
14357         for(i=0; i<storedGames; i++) {
14358             if(savedDetails[i])
14359                 free(savedDetails[i]);
14360             savedDetails[i] = NULL;
14361         }
14362         for(i=framePtr; i<MAX_MOVES; i++) {
14363                 if(commentList[i]) free(commentList[i]);
14364                 commentList[i] = NULL;
14365         }
14366         framePtr = MAX_MOVES-1;
14367         storedGames = 0;
14368 }