fixed Makefile.am to handle config file correctly
[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 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
253
254 /* States for ics_getting_history */
255 #define H_FALSE 0
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
261
262 /* whosays values for GameEnds */
263 #define GE_ICS 0
264 #define GE_ENGINE 1
265 #define GE_PLAYER 2
266 #define GE_FILE 3
267 #define GE_XBOARD 4
268 #define GE_ENGINE1 5
269 #define GE_ENGINE2 6
270
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
273
274 /* Different types of move when calling RegisterMove */
275 #define CMAIL_MOVE   0
276 #define CMAIL_RESIGN 1
277 #define CMAIL_DRAW   2
278 #define CMAIL_ACCEPT 3
279
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
284
285 /* Telnet protocol constants */
286 #define TN_WILL 0373
287 #define TN_WONT 0374
288 #define TN_DO   0375
289 #define TN_DONT 0376
290 #define TN_IAC  0377
291 #define TN_ECHO 0001
292 #define TN_SGA  0003
293 #define TN_PORT 23
294
295 /* [AS] */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 {
298     assert( dst != NULL );
299     assert( src != NULL );
300     assert( count > 0 );
301
302     strncpy( dst, src, count );
303     dst[ count-1 ] = '\0';
304     return dst;
305 }
306
307 /* Some compiler can't cast u64 to double
308  * This function do the job for us:
309
310  * We use the highest bit for cast, this only
311  * works if the highest bit is not
312  * in use (This should not happen)
313  *
314  * We used this for all compiler
315  */
316 double
317 u64ToDouble(u64 value)
318 {
319   double r;
320   u64 tmp = value & u64Const(0x7fffffffffffffff);
321   r = (double)(s64)tmp;
322   if (value & u64Const(0x8000000000000000))
323        r +=  9.2233720368547758080e18; /* 2^63 */
324  return r;
325 }
326
327 /* Fake up flags for now, as we aren't keeping track of castling
328    availability yet. [HGM] Change of logic: the flag now only
329    indicates the type of castlings allowed by the rule of the game.
330    The actual rights themselves are maintained in the array
331    castlingRights, as part of the game history, and are not probed
332    by this function.
333  */
334 int
335 PosFlags(index)
336 {
337   int flags = F_ALL_CASTLE_OK;
338   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339   switch (gameInfo.variant) {
340   case VariantSuicide:
341     flags &= ~F_ALL_CASTLE_OK;
342   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343     flags |= F_IGNORE_CHECK;
344   case VariantLosers:
345     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
346     break;
347   case VariantAtomic:
348     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349     break;
350   case VariantKriegspiel:
351     flags |= F_KRIEGSPIEL_CAPTURE;
352     break;
353   case VariantCapaRandom: 
354   case VariantFischeRandom:
355     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356   case VariantNoCastle:
357   case VariantShatranj:
358   case VariantCourier:
359   case VariantMakruk:
360     flags &= ~F_ALL_CASTLE_OK;
361     break;
362   default:
363     break;
364   }
365   return flags;
366 }
367
368 FILE *gameFileFP, *debugFP;
369
370 /* 
371     [AS] Note: sometimes, the sscanf() function is used to parse the input
372     into a fixed-size buffer. Because of this, we must be prepared to
373     receive strings as long as the size of the input buffer, which is currently
374     set to 4K for Windows and 8K for the rest.
375     So, we must either allocate sufficiently large buffers here, or
376     reduce the size of the input buffer in the input reading part.
377 */
378
379 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
380 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
381 char thinkOutput1[MSG_SIZ*10];
382
383 ChessProgramState first, second;
384
385 /* premove variables */
386 int premoveToX = 0;
387 int premoveToY = 0;
388 int premoveFromX = 0;
389 int premoveFromY = 0;
390 int premovePromoChar = 0;
391 int gotPremove = 0;
392 Boolean alarmSounded;
393 /* end premove variables */
394
395 char *ics_prefix = "$";
396 int ics_type = ICS_GENERIC;
397
398 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
399 int pauseExamForwardMostMove = 0;
400 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
401 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
402 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
403 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
404 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
405 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
406 int whiteFlag = FALSE, blackFlag = FALSE;
407 int userOfferedDraw = FALSE;
408 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
409 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
410 int cmailMoveType[CMAIL_MAX_GAMES];
411 long ics_clock_paused = 0;
412 ProcRef icsPR = NoProc, cmailPR = NoProc;
413 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
414 GameMode gameMode = BeginningOfGame;
415 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
416 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
417 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
418 int hiddenThinkOutputState = 0; /* [AS] */
419 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
420 int adjudicateLossPlies = 6;
421 char white_holding[64], black_holding[64];
422 TimeMark lastNodeCountTime;
423 long lastNodeCount=0;
424 int have_sent_ICS_logon = 0;
425 int movesPerSession;
426 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
427 long timeControl_2; /* [AS] Allow separate time controls */
428 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
429 long timeRemaining[2][MAX_MOVES];
430 int matchGame = 0;
431 TimeMark programStartTime;
432 char ics_handle[MSG_SIZ];
433 int have_set_title = 0;
434
435 /* animateTraining preserves the state of appData.animate
436  * when Training mode is activated. This allows the
437  * response to be animated when appData.animate == TRUE and
438  * appData.animateDragging == TRUE.
439  */
440 Boolean animateTraining;
441
442 GameInfo gameInfo;
443
444 AppData appData;
445
446 Board boards[MAX_MOVES];
447 /* [HGM] Following 7 needed for accurate legality tests: */
448 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
449 signed char  initialRights[BOARD_FILES];
450 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
451 int   initialRulePlies, FENrulePlies;
452 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int loadFlag = 0; 
454 int shuffleOpenings;
455 int mute; // mute all sounds
456
457 // [HGM] vari: next 12 to save and restore variations
458 #define MAX_VARIATIONS 10
459 int framePtr = MAX_MOVES-1; // points to free stack entry
460 int storedGames = 0;
461 int savedFirst[MAX_VARIATIONS];
462 int savedLast[MAX_VARIATIONS];
463 int savedFramePtr[MAX_VARIATIONS];
464 char *savedDetails[MAX_VARIATIONS];
465 ChessMove savedResult[MAX_VARIATIONS];
466
467 void PushTail P((int firstMove, int lastMove));
468 Boolean PopTail P((Boolean annotate));
469 void CleanupTail P((void));
470
471 ChessSquare  FIDEArray[2][BOARD_FILES] = {
472     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475         BlackKing, BlackBishop, BlackKnight, BlackRook }
476 };
477
478 ChessSquare twoKingsArray[2][BOARD_FILES] = {
479     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482         BlackKing, BlackKing, BlackKnight, BlackRook }
483 };
484
485 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
486     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488     { BlackRook, BlackMan, BlackBishop, BlackQueen,
489         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
490 };
491
492 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
493     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
494         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
496         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
497 };
498
499 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
504 };
505
506 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
507     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
508         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackMan, BlackFerz,
510         BlackKing, BlackMan, BlackKnight, BlackRook }
511 };
512
513
514 #if (BOARD_FILES>=10)
515 ChessSquare ShogiArray[2][BOARD_FILES] = {
516     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
517         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
518     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
519         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
520 };
521
522 ChessSquare XiangqiArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
524         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
526         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
527 };
528
529 ChessSquare CapablancaArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
531         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
533         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
534 };
535
536 ChessSquare GreatArray[2][BOARD_FILES] = {
537     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
538         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
539     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
540         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
541 };
542
543 ChessSquare JanusArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
545         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
546     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
547         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
548 };
549
550 #ifdef GOTHIC
551 ChessSquare GothicArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
553         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
555         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !GOTHIC
558 #define GothicArray CapablancaArray
559 #endif // !GOTHIC
560
561 #ifdef FALCON
562 ChessSquare FalconArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
564         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
566         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
567 };
568 #else // !FALCON
569 #define FalconArray CapablancaArray
570 #endif // !FALCON
571
572 #else // !(BOARD_FILES>=10)
573 #define XiangqiPosition FIDEArray
574 #define CapablancaArray FIDEArray
575 #define GothicArray FIDEArray
576 #define GreatArray FIDEArray
577 #endif // !(BOARD_FILES>=10)
578
579 #if (BOARD_FILES>=12)
580 ChessSquare CourierArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
582         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
584         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
585 };
586 #else // !(BOARD_FILES>=12)
587 #define CourierArray CapablancaArray
588 #endif // !(BOARD_FILES>=12)
589
590
591 Board initialPosition;
592
593
594 /* Convert str to a rating. Checks for special cases of "----",
595
596    "++++", etc. Also strips ()'s */
597 int
598 string_to_rating(str)
599   char *str;
600 {
601   while(*str && !isdigit(*str)) ++str;
602   if (!*str)
603     return 0;   /* One of the special "no rating" cases */
604   else
605     return atoi(str);
606 }
607
608 void
609 ClearProgramStats()
610 {
611     /* Init programStats */
612     programStats.movelist[0] = 0;
613     programStats.depth = 0;
614     programStats.nr_moves = 0;
615     programStats.moves_left = 0;
616     programStats.nodes = 0;
617     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
618     programStats.score = 0;
619     programStats.got_only_move = 0;
620     programStats.got_fail = 0;
621     programStats.line_is_book = 0;
622 }
623
624 void
625 InitBackEnd1()
626 {
627     int matched, min, sec;
628
629     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
630
631     GetTimeMark(&programStartTime);
632     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
633
634     ClearProgramStats();
635     programStats.ok_to_send = 1;
636     programStats.seen_stat = 0;
637
638     /*
639      * Initialize game list
640      */
641     ListNew(&gameList);
642
643
644     /*
645      * Internet chess server status
646      */
647     if (appData.icsActive) {
648         appData.matchMode = FALSE;
649         appData.matchGames = 0;
650 #if ZIPPY       
651         appData.noChessProgram = !appData.zippyPlay;
652 #else
653         appData.zippyPlay = FALSE;
654         appData.zippyTalk = FALSE;
655         appData.noChessProgram = TRUE;
656 #endif
657         if (*appData.icsHelper != NULLCHAR) {
658             appData.useTelnet = TRUE;
659             appData.telnetProgram = appData.icsHelper;
660         }
661     } else {
662         appData.zippyTalk = appData.zippyPlay = FALSE;
663     }
664
665     /* [AS] Initialize pv info list [HGM] and game state */
666     {
667         int i, j;
668
669         for( i=0; i<=framePtr; i++ ) {
670             pvInfoList[i].depth = -1;
671             boards[i][EP_STATUS] = EP_NONE;
672             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
673         }
674     }
675
676     /*
677      * Parse timeControl resource
678      */
679     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
680                           appData.movesPerSession)) {
681         char buf[MSG_SIZ];
682         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
683         DisplayFatalError(buf, 0, 2);
684     }
685
686     /*
687      * Parse searchTime resource
688      */
689     if (*appData.searchTime != NULLCHAR) {
690         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
691         if (matched == 1) {
692             searchTime = min * 60;
693         } else if (matched == 2) {
694             searchTime = min * 60 + sec;
695         } else {
696             char buf[MSG_SIZ];
697             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
698             DisplayFatalError(buf, 0, 2);
699         }
700     }
701
702     /* [AS] Adjudication threshold */
703     adjudicateLossThreshold = appData.adjudicateLossThreshold;
704     
705     first.which = "first";
706     second.which = "second";
707     first.maybeThinking = second.maybeThinking = FALSE;
708     first.pr = second.pr = NoProc;
709     first.isr = second.isr = NULL;
710     first.sendTime = second.sendTime = 2;
711     first.sendDrawOffers = 1;
712     if (appData.firstPlaysBlack) {
713         first.twoMachinesColor = "black\n";
714         second.twoMachinesColor = "white\n";
715     } else {
716         first.twoMachinesColor = "white\n";
717         second.twoMachinesColor = "black\n";
718     }
719     first.program = appData.firstChessProgram;
720     second.program = appData.secondChessProgram;
721     first.host = appData.firstHost;
722     second.host = appData.secondHost;
723     first.dir = appData.firstDirectory;
724     second.dir = appData.secondDirectory;
725     first.other = &second;
726     second.other = &first;
727     first.initString = appData.initString;
728     second.initString = appData.secondInitString;
729     first.computerString = appData.firstComputerString;
730     second.computerString = appData.secondComputerString;
731     first.useSigint = second.useSigint = TRUE;
732     first.useSigterm = second.useSigterm = TRUE;
733     first.reuse = appData.reuseFirst;
734     second.reuse = appData.reuseSecond;
735     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
736     second.nps = appData.secondNPS;
737     first.useSetboard = second.useSetboard = FALSE;
738     first.useSAN = second.useSAN = FALSE;
739     first.usePing = second.usePing = FALSE;
740     first.lastPing = second.lastPing = 0;
741     first.lastPong = second.lastPong = 0;
742     first.usePlayother = second.usePlayother = FALSE;
743     first.useColors = second.useColors = TRUE;
744     first.useUsermove = second.useUsermove = FALSE;
745     first.sendICS = second.sendICS = FALSE;
746     first.sendName = second.sendName = appData.icsActive;
747     first.sdKludge = second.sdKludge = FALSE;
748     first.stKludge = second.stKludge = FALSE;
749     TidyProgramName(first.program, first.host, first.tidy);
750     TidyProgramName(second.program, second.host, second.tidy);
751     first.matchWins = second.matchWins = 0;
752     strcpy(first.variants, appData.variant);
753     strcpy(second.variants, appData.variant);
754     first.analysisSupport = second.analysisSupport = 2; /* detect */
755     first.analyzing = second.analyzing = FALSE;
756     first.initDone = second.initDone = FALSE;
757
758     /* New features added by Tord: */
759     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
760     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
761     /* End of new features added by Tord. */
762     first.fenOverride  = appData.fenOverride1;
763     second.fenOverride = appData.fenOverride2;
764
765     /* [HGM] time odds: set factor for each machine */
766     first.timeOdds  = appData.firstTimeOdds;
767     second.timeOdds = appData.secondTimeOdds;
768     { float norm = 1;
769         if(appData.timeOddsMode) {
770             norm = first.timeOdds;
771             if(norm > second.timeOdds) norm = second.timeOdds;
772         }
773         first.timeOdds /= norm;
774         second.timeOdds /= norm;
775     }
776
777     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
778     first.accumulateTC = appData.firstAccumulateTC;
779     second.accumulateTC = appData.secondAccumulateTC;
780     first.maxNrOfSessions = second.maxNrOfSessions = 1;
781
782     /* [HGM] debug */
783     first.debug = second.debug = FALSE;
784     first.supportsNPS = second.supportsNPS = UNKNOWN;
785
786     /* [HGM] options */
787     first.optionSettings  = appData.firstOptions;
788     second.optionSettings = appData.secondOptions;
789
790     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
791     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
792     first.isUCI = appData.firstIsUCI; /* [AS] */
793     second.isUCI = appData.secondIsUCI; /* [AS] */
794     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
795     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
796
797     if (appData.firstProtocolVersion > PROTOVER ||
798         appData.firstProtocolVersion < 1) {
799       char buf[MSG_SIZ];
800       sprintf(buf, _("protocol version %d not supported"),
801               appData.firstProtocolVersion);
802       DisplayFatalError(buf, 0, 2);
803     } else {
804       first.protocolVersion = appData.firstProtocolVersion;
805     }
806
807     if (appData.secondProtocolVersion > PROTOVER ||
808         appData.secondProtocolVersion < 1) {
809       char buf[MSG_SIZ];
810       sprintf(buf, _("protocol version %d not supported"),
811               appData.secondProtocolVersion);
812       DisplayFatalError(buf, 0, 2);
813     } else {
814       second.protocolVersion = appData.secondProtocolVersion;
815     }
816
817     if (appData.icsActive) {
818         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
819 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
820     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
821         appData.clockMode = FALSE;
822         first.sendTime = second.sendTime = 0;
823     }
824     
825 #if ZIPPY
826     /* Override some settings from environment variables, for backward
827        compatibility.  Unfortunately it's not feasible to have the env
828        vars just set defaults, at least in xboard.  Ugh.
829     */
830     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
831       ZippyInit();
832     }
833 #endif
834     
835     if (appData.noChessProgram) {
836         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
837         sprintf(programVersion, "%s", PACKAGE_STRING);
838     } else {
839       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
840       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
841       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
842     }
843
844     if (!appData.icsActive) {
845       char buf[MSG_SIZ];
846       /* Check for variants that are supported only in ICS mode,
847          or not at all.  Some that are accepted here nevertheless
848          have bugs; see comments below.
849       */
850       VariantClass variant = StringToVariant(appData.variant);
851       switch (variant) {
852       case VariantBughouse:     /* need four players and two boards */
853       case VariantKriegspiel:   /* need to hide pieces and move details */
854       /* case VariantFischeRandom: (Fabien: moved below) */
855         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
856         DisplayFatalError(buf, 0, 2);
857         return;
858
859       case VariantUnknown:
860       case VariantLoadable:
861       case Variant29:
862       case Variant30:
863       case Variant31:
864       case Variant32:
865       case Variant33:
866       case Variant34:
867       case Variant35:
868       case Variant36:
869       default:
870         sprintf(buf, _("Unknown variant name %s"), appData.variant);
871         DisplayFatalError(buf, 0, 2);
872         return;
873
874       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
875       case VariantFairy:      /* [HGM] TestLegality definitely off! */
876       case VariantGothic:     /* [HGM] should work */
877       case VariantCapablanca: /* [HGM] should work */
878       case VariantCourier:    /* [HGM] initial forced moves not implemented */
879       case VariantShogi:      /* [HGM] drops not tested for legality */
880       case VariantKnightmate: /* [HGM] should work */
881       case VariantCylinder:   /* [HGM] untested */
882       case VariantFalcon:     /* [HGM] untested */
883       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
884                                  offboard interposition not understood */
885       case VariantNormal:     /* definitely works! */
886       case VariantWildCastle: /* pieces not automatically shuffled */
887       case VariantNoCastle:   /* pieces not automatically shuffled */
888       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
889       case VariantLosers:     /* should work except for win condition,
890                                  and doesn't know captures are mandatory */
891       case VariantSuicide:    /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantGiveaway:   /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantTwoKings:   /* should work */
896       case VariantAtomic:     /* should work except for win condition */
897       case Variant3Check:     /* should work except for win condition */
898       case VariantShatranj:   /* should work except for all win conditions */
899       case VariantMakruk:     /* should work except for daw countdown */
900       case VariantBerolina:   /* might work if TestLegality is off */
901       case VariantCapaRandom: /* should work */
902       case VariantJanus:      /* should work */
903       case VariantSuper:      /* experimental */
904       case VariantGreat:      /* experimental, requires legality testing to be off */
905         break;
906       }
907     }
908
909     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
910     InitEngineUCI( installDir, &second );
911 }
912
913 int NextIntegerFromString( char ** str, long * value )
914 {
915     int result = -1;
916     char * s = *str;
917
918     while( *s == ' ' || *s == '\t' ) {
919         s++;
920     }
921
922     *value = 0;
923
924     if( *s >= '0' && *s <= '9' ) {
925         while( *s >= '0' && *s <= '9' ) {
926             *value = *value * 10 + (*s - '0');
927             s++;
928         }
929
930         result = 0;
931     }
932
933     *str = s;
934
935     return result;
936 }
937
938 int NextTimeControlFromString( char ** str, long * value )
939 {
940     long temp;
941     int result = NextIntegerFromString( str, &temp );
942
943     if( result == 0 ) {
944         *value = temp * 60; /* Minutes */
945         if( **str == ':' ) {
946             (*str)++;
947             result = NextIntegerFromString( str, &temp );
948             *value += temp; /* Seconds */
949         }
950     }
951
952     return result;
953 }
954
955 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
956 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
957     int result = -1; long temp, temp2;
958
959     if(**str != '+') return -1; // old params remain in force!
960     (*str)++;
961     if( NextTimeControlFromString( str, &temp ) ) return -1;
962
963     if(**str != '/') {
964         /* time only: incremental or sudden-death time control */
965         if(**str == '+') { /* increment follows; read it */
966             (*str)++;
967             if(result = NextIntegerFromString( str, &temp2)) return -1;
968             *inc = temp2 * 1000;
969         } else *inc = 0;
970         *moves = 0; *tc = temp * 1000; 
971         return 0;
972     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
973
974     (*str)++; /* classical time control */
975     result = NextTimeControlFromString( str, &temp2);
976     if(result == 0) {
977         *moves = temp/60;
978         *tc    = temp2 * 1000;
979         *inc   = 0;
980     }
981     return result;
982 }
983
984 int GetTimeQuota(int movenr)
985 {   /* [HGM] get time to add from the multi-session time-control string */
986     int moves=1; /* kludge to force reading of first session */
987     long time, increment;
988     char *s = fullTimeControlString;
989
990     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
991     do {
992         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
993         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
994         if(movenr == -1) return time;    /* last move before new session     */
995         if(!moves) return increment;     /* current session is incremental   */
996         if(movenr >= 0) movenr -= moves; /* we already finished this session */
997     } while(movenr >= -1);               /* try again for next session       */
998
999     return 0; // no new time quota on this move
1000 }
1001
1002 int
1003 ParseTimeControl(tc, ti, mps)
1004      char *tc;
1005      int ti;
1006      int mps;
1007 {
1008   long tc1;
1009   long tc2;
1010   char buf[MSG_SIZ];
1011   
1012   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1013   if(ti > 0) {
1014     if(mps)
1015       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1016     else sprintf(buf, "+%s+%d", tc, ti);
1017   } else {
1018     if(mps)
1019              sprintf(buf, "+%d/%s", mps, tc);
1020     else sprintf(buf, "+%s", tc);
1021   }
1022   fullTimeControlString = StrSave(buf);
1023   
1024   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1025     return FALSE;
1026   }
1027   
1028   if( *tc == '/' ) {
1029     /* Parse second time control */
1030     tc++;
1031     
1032     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1033       return FALSE;
1034     }
1035     
1036     if( tc2 == 0 ) {
1037       return FALSE;
1038     }
1039     
1040     timeControl_2 = tc2 * 1000;
1041   }
1042   else {
1043     timeControl_2 = 0;
1044   }
1045   
1046   if( tc1 == 0 ) {
1047     return FALSE;
1048   }
1049   
1050   timeControl = tc1 * 1000;
1051   
1052   if (ti >= 0) {
1053     timeIncrement = ti * 1000;  /* convert to ms */
1054     movesPerSession = 0;
1055   } else {
1056     timeIncrement = 0;
1057     movesPerSession = mps;
1058   }
1059   return TRUE;
1060 }
1061
1062 void
1063 InitBackEnd2()
1064 {
1065     if (appData.debugMode) {
1066         fprintf(debugFP, "%s\n", programVersion);
1067     }
1068
1069     set_cont_sequence(appData.wrapContSeq);
1070     if (appData.matchGames > 0) {
1071         appData.matchMode = TRUE;
1072     } else if (appData.matchMode) {
1073         appData.matchGames = 1;
1074     }
1075     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1076         appData.matchGames = appData.sameColorGames;
1077     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1078         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1079         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1080     }
1081     Reset(TRUE, FALSE);
1082     if (appData.noChessProgram || first.protocolVersion == 1) {
1083       InitBackEnd3();
1084     } else {
1085       /* kludge: allow timeout for initial "feature" commands */
1086       FreezeUI();
1087       DisplayMessage("", _("Starting chess program"));
1088       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1089     }
1090 }
1091
1092 void
1093 InitBackEnd3 P((void))
1094 {
1095     GameMode initialMode;
1096     char buf[MSG_SIZ];
1097     int err;
1098
1099     InitChessProgram(&first, startedFromSetupPosition);
1100
1101
1102     if (appData.icsActive) {
1103 #ifdef WIN32
1104         /* [DM] Make a console window if needed [HGM] merged ifs */
1105         ConsoleCreate(); 
1106 #endif
1107         err = establish();
1108         if (err != 0) {
1109             if (*appData.icsCommPort != NULLCHAR) {
1110                 sprintf(buf, _("Could not open comm port %s"),  
1111                         appData.icsCommPort);
1112             } else {
1113                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1114                         appData.icsHost, appData.icsPort);
1115             }
1116             DisplayFatalError(buf, err, 1);
1117             return;
1118         }
1119         SetICSMode();
1120         telnetISR =
1121           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1122         fromUserISR =
1123           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1124     } else if (appData.noChessProgram) {
1125         SetNCPMode();
1126     } else {
1127         SetGNUMode();
1128     }
1129
1130     if (*appData.cmailGameName != NULLCHAR) {
1131         SetCmailMode();
1132         OpenLoopback(&cmailPR);
1133         cmailISR =
1134           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1135     }
1136     
1137     ThawUI();
1138     DisplayMessage("", "");
1139     if (StrCaseCmp(appData.initialMode, "") == 0) {
1140       initialMode = BeginningOfGame;
1141     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1142       initialMode = TwoMachinesPlay;
1143     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1144       initialMode = AnalyzeFile; 
1145     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1146       initialMode = AnalyzeMode;
1147     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1148       initialMode = MachinePlaysWhite;
1149     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1150       initialMode = MachinePlaysBlack;
1151     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1152       initialMode = EditGame;
1153     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1154       initialMode = EditPosition;
1155     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1156       initialMode = Training;
1157     } else {
1158       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1159       DisplayFatalError(buf, 0, 2);
1160       return;
1161     }
1162
1163     if (appData.matchMode) {
1164         /* Set up machine vs. machine match */
1165         if (appData.noChessProgram) {
1166             DisplayFatalError(_("Can't have a match with no chess programs"),
1167                               0, 2);
1168             return;
1169         }
1170         matchMode = TRUE;
1171         matchGame = 1;
1172         if (*appData.loadGameFile != NULLCHAR) {
1173             int index = appData.loadGameIndex; // [HGM] autoinc
1174             if(index<0) lastIndex = index = 1;
1175             if (!LoadGameFromFile(appData.loadGameFile,
1176                                   index,
1177                                   appData.loadGameFile, FALSE)) {
1178                 DisplayFatalError(_("Bad game file"), 0, 1);
1179                 return;
1180             }
1181         } else if (*appData.loadPositionFile != NULLCHAR) {
1182             int index = appData.loadPositionIndex; // [HGM] autoinc
1183             if(index<0) lastIndex = index = 1;
1184             if (!LoadPositionFromFile(appData.loadPositionFile,
1185                                       index,
1186                                       appData.loadPositionFile)) {
1187                 DisplayFatalError(_("Bad position file"), 0, 1);
1188                 return;
1189             }
1190         }
1191         TwoMachinesEvent();
1192     } else if (*appData.cmailGameName != NULLCHAR) {
1193         /* Set up cmail mode */
1194         ReloadCmailMsgEvent(TRUE);
1195     } else {
1196         /* Set up other modes */
1197         if (initialMode == AnalyzeFile) {
1198           if (*appData.loadGameFile == NULLCHAR) {
1199             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1200             return;
1201           }
1202         }
1203         if (*appData.loadGameFile != NULLCHAR) {
1204             (void) LoadGameFromFile(appData.loadGameFile,
1205                                     appData.loadGameIndex,
1206                                     appData.loadGameFile, TRUE);
1207         } else if (*appData.loadPositionFile != NULLCHAR) {
1208             (void) LoadPositionFromFile(appData.loadPositionFile,
1209                                         appData.loadPositionIndex,
1210                                         appData.loadPositionFile);
1211             /* [HGM] try to make self-starting even after FEN load */
1212             /* to allow automatic setup of fairy variants with wtm */
1213             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1214                 gameMode = BeginningOfGame;
1215                 setboardSpoiledMachineBlack = 1;
1216             }
1217             /* [HGM] loadPos: make that every new game uses the setup */
1218             /* from file as long as we do not switch variant          */
1219             if(!blackPlaysFirst) {
1220                 startedFromPositionFile = TRUE;
1221                 CopyBoard(filePosition, boards[0]);
1222             }
1223         }
1224         if (initialMode == AnalyzeMode) {
1225           if (appData.noChessProgram) {
1226             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1227             return;
1228           }
1229           if (appData.icsActive) {
1230             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1231             return;
1232           }
1233           AnalyzeModeEvent();
1234         } else if (initialMode == AnalyzeFile) {
1235           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1236           ShowThinkingEvent();
1237           AnalyzeFileEvent();
1238           AnalysisPeriodicEvent(1);
1239         } else if (initialMode == MachinePlaysWhite) {
1240           if (appData.noChessProgram) {
1241             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1242                               0, 2);
1243             return;
1244           }
1245           if (appData.icsActive) {
1246             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1247                               0, 2);
1248             return;
1249           }
1250           MachineWhiteEvent();
1251         } else if (initialMode == MachinePlaysBlack) {
1252           if (appData.noChessProgram) {
1253             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1254                               0, 2);
1255             return;
1256           }
1257           if (appData.icsActive) {
1258             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1259                               0, 2);
1260             return;
1261           }
1262           MachineBlackEvent();
1263         } else if (initialMode == TwoMachinesPlay) {
1264           if (appData.noChessProgram) {
1265             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1266                               0, 2);
1267             return;
1268           }
1269           if (appData.icsActive) {
1270             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1271                               0, 2);
1272             return;
1273           }
1274           TwoMachinesEvent();
1275         } else if (initialMode == EditGame) {
1276           EditGameEvent();
1277         } else if (initialMode == EditPosition) {
1278           EditPositionEvent();
1279         } else if (initialMode == Training) {
1280           if (*appData.loadGameFile == NULLCHAR) {
1281             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1282             return;
1283           }
1284           TrainingEvent();
1285         }
1286     }
1287 }
1288
1289 /*
1290  * Establish will establish a contact to a remote host.port.
1291  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1292  *  used to talk to the host.
1293  * Returns 0 if okay, error code if not.
1294  */
1295 int
1296 establish()
1297 {
1298     char buf[MSG_SIZ];
1299
1300     if (*appData.icsCommPort != NULLCHAR) {
1301         /* Talk to the host through a serial comm port */
1302         return OpenCommPort(appData.icsCommPort, &icsPR);
1303
1304     } else if (*appData.gateway != NULLCHAR) {
1305         if (*appData.remoteShell == NULLCHAR) {
1306             /* Use the rcmd protocol to run telnet program on a gateway host */
1307             snprintf(buf, sizeof(buf), "%s %s %s",
1308                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1309             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1310
1311         } else {
1312             /* Use the rsh program to run telnet program on a gateway host */
1313             if (*appData.remoteUser == NULLCHAR) {
1314                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1315                         appData.gateway, appData.telnetProgram,
1316                         appData.icsHost, appData.icsPort);
1317             } else {
1318                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1319                         appData.remoteShell, appData.gateway, 
1320                         appData.remoteUser, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             }
1323             return StartChildProcess(buf, "", &icsPR);
1324
1325         }
1326     } else if (appData.useTelnet) {
1327         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1328
1329     } else {
1330         /* TCP socket interface differs somewhat between
1331            Unix and NT; handle details in the front end.
1332            */
1333         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1334     }
1335 }
1336
1337 void
1338 show_bytes(fp, buf, count)
1339      FILE *fp;
1340      char *buf;
1341      int count;
1342 {
1343     while (count--) {
1344         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1345             fprintf(fp, "\\%03o", *buf & 0xff);
1346         } else {
1347             putc(*buf, fp);
1348         }
1349         buf++;
1350     }
1351     fflush(fp);
1352 }
1353
1354 /* Returns an errno value */
1355 int
1356 OutputMaybeTelnet(pr, message, count, outError)
1357      ProcRef pr;
1358      char *message;
1359      int count;
1360      int *outError;
1361 {
1362     char buf[8192], *p, *q, *buflim;
1363     int left, newcount, outcount;
1364
1365     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1366         *appData.gateway != NULLCHAR) {
1367         if (appData.debugMode) {
1368             fprintf(debugFP, ">ICS: ");
1369             show_bytes(debugFP, message, count);
1370             fprintf(debugFP, "\n");
1371         }
1372         return OutputToProcess(pr, message, count, outError);
1373     }
1374
1375     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1376     p = message;
1377     q = buf;
1378     left = count;
1379     newcount = 0;
1380     while (left) {
1381         if (q >= buflim) {
1382             if (appData.debugMode) {
1383                 fprintf(debugFP, ">ICS: ");
1384                 show_bytes(debugFP, buf, newcount);
1385                 fprintf(debugFP, "\n");
1386             }
1387             outcount = OutputToProcess(pr, buf, newcount, outError);
1388             if (outcount < newcount) return -1; /* to be sure */
1389             q = buf;
1390             newcount = 0;
1391         }
1392         if (*p == '\n') {
1393             *q++ = '\r';
1394             newcount++;
1395         } else if (((unsigned char) *p) == TN_IAC) {
1396             *q++ = (char) TN_IAC;
1397             newcount ++;
1398         }
1399         *q++ = *p++;
1400         newcount++;
1401         left--;
1402     }
1403     if (appData.debugMode) {
1404         fprintf(debugFP, ">ICS: ");
1405         show_bytes(debugFP, buf, newcount);
1406         fprintf(debugFP, "\n");
1407     }
1408     outcount = OutputToProcess(pr, buf, newcount, outError);
1409     if (outcount < newcount) return -1; /* to be sure */
1410     return count;
1411 }
1412
1413 void
1414 read_from_player(isr, closure, message, count, error)
1415      InputSourceRef isr;
1416      VOIDSTAR closure;
1417      char *message;
1418      int count;
1419      int error;
1420 {
1421     int outError, outCount;
1422     static int gotEof = 0;
1423
1424     /* Pass data read from player on to ICS */
1425     if (count > 0) {
1426         gotEof = 0;
1427         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1428         if (outCount < count) {
1429             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1430         }
1431     } else if (count < 0) {
1432         RemoveInputSource(isr);
1433         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1434     } else if (gotEof++ > 0) {
1435         RemoveInputSource(isr);
1436         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1437     }
1438 }
1439
1440 void
1441 KeepAlive()
1442 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1443     SendToICS("date\n");
1444     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1445 }
1446
1447 /* added routine for printf style output to ics */
1448 void ics_printf(char *format, ...)
1449 {
1450     char buffer[MSG_SIZ];
1451     va_list args;
1452
1453     va_start(args, format);
1454     vsnprintf(buffer, sizeof(buffer), format, args);
1455     buffer[sizeof(buffer)-1] = '\0';
1456     SendToICS(buffer);
1457     va_end(args);
1458 }
1459
1460 void
1461 SendToICS(s)
1462      char *s;
1463 {
1464     int count, outCount, outError;
1465
1466     if (icsPR == NULL) return;
1467
1468     count = strlen(s);
1469     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1470     if (outCount < count) {
1471         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1472     }
1473 }
1474
1475 /* This is used for sending logon scripts to the ICS. Sending
1476    without a delay causes problems when using timestamp on ICC
1477    (at least on my machine). */
1478 void
1479 SendToICSDelayed(s,msdelay)
1480      char *s;
1481      long msdelay;
1482 {
1483     int count, outCount, outError;
1484
1485     if (icsPR == NULL) return;
1486
1487     count = strlen(s);
1488     if (appData.debugMode) {
1489         fprintf(debugFP, ">ICS: ");
1490         show_bytes(debugFP, s, count);
1491         fprintf(debugFP, "\n");
1492     }
1493     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1494                                       msdelay);
1495     if (outCount < count) {
1496         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1497     }
1498 }
1499
1500
1501 /* Remove all highlighting escape sequences in s
1502    Also deletes any suffix starting with '(' 
1503    */
1504 char *
1505 StripHighlightAndTitle(s)
1506      char *s;
1507 {
1508     static char retbuf[MSG_SIZ];
1509     char *p = retbuf;
1510
1511     while (*s != NULLCHAR) {
1512         while (*s == '\033') {
1513             while (*s != NULLCHAR && !isalpha(*s)) s++;
1514             if (*s != NULLCHAR) s++;
1515         }
1516         while (*s != NULLCHAR && *s != '\033') {
1517             if (*s == '(' || *s == '[') {
1518                 *p = NULLCHAR;
1519                 return retbuf;
1520             }
1521             *p++ = *s++;
1522         }
1523     }
1524     *p = NULLCHAR;
1525     return retbuf;
1526 }
1527
1528 /* Remove all highlighting escape sequences in s */
1529 char *
1530 StripHighlight(s)
1531      char *s;
1532 {
1533     static char retbuf[MSG_SIZ];
1534     char *p = retbuf;
1535
1536     while (*s != NULLCHAR) {
1537         while (*s == '\033') {
1538             while (*s != NULLCHAR && !isalpha(*s)) s++;
1539             if (*s != NULLCHAR) s++;
1540         }
1541         while (*s != NULLCHAR && *s != '\033') {
1542             *p++ = *s++;
1543         }
1544     }
1545     *p = NULLCHAR;
1546     return retbuf;
1547 }
1548
1549 char *variantNames[] = VARIANT_NAMES;
1550 char *
1551 VariantName(v)
1552      VariantClass v;
1553 {
1554     return variantNames[v];
1555 }
1556
1557
1558 /* Identify a variant from the strings the chess servers use or the
1559    PGN Variant tag names we use. */
1560 VariantClass
1561 StringToVariant(e)
1562      char *e;
1563 {
1564     char *p;
1565     int wnum = -1;
1566     VariantClass v = VariantNormal;
1567     int i, found = FALSE;
1568     char buf[MSG_SIZ];
1569
1570     if (!e) return v;
1571
1572     /* [HGM] skip over optional board-size prefixes */
1573     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1574         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1575         while( *e++ != '_');
1576     }
1577
1578     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1579         v = VariantNormal;
1580         found = TRUE;
1581     } else
1582     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1583       if (StrCaseStr(e, variantNames[i])) {
1584         v = (VariantClass) i;
1585         found = TRUE;
1586         break;
1587       }
1588     }
1589
1590     if (!found) {
1591       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1592           || StrCaseStr(e, "wild/fr") 
1593           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1594         v = VariantFischeRandom;
1595       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1596                  (i = 1, p = StrCaseStr(e, "w"))) {
1597         p += i;
1598         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1599         if (isdigit(*p)) {
1600           wnum = atoi(p);
1601         } else {
1602           wnum = -1;
1603         }
1604         switch (wnum) {
1605         case 0: /* FICS only, actually */
1606         case 1:
1607           /* Castling legal even if K starts on d-file */
1608           v = VariantWildCastle;
1609           break;
1610         case 2:
1611         case 3:
1612         case 4:
1613           /* Castling illegal even if K & R happen to start in
1614              normal positions. */
1615           v = VariantNoCastle;
1616           break;
1617         case 5:
1618         case 7:
1619         case 8:
1620         case 10:
1621         case 11:
1622         case 12:
1623         case 13:
1624         case 14:
1625         case 15:
1626         case 18:
1627         case 19:
1628           /* Castling legal iff K & R start in normal positions */
1629           v = VariantNormal;
1630           break;
1631         case 6:
1632         case 20:
1633         case 21:
1634           /* Special wilds for position setup; unclear what to do here */
1635           v = VariantLoadable;
1636           break;
1637         case 9:
1638           /* Bizarre ICC game */
1639           v = VariantTwoKings;
1640           break;
1641         case 16:
1642           v = VariantKriegspiel;
1643           break;
1644         case 17:
1645           v = VariantLosers;
1646           break;
1647         case 22:
1648           v = VariantFischeRandom;
1649           break;
1650         case 23:
1651           v = VariantCrazyhouse;
1652           break;
1653         case 24:
1654           v = VariantBughouse;
1655           break;
1656         case 25:
1657           v = Variant3Check;
1658           break;
1659         case 26:
1660           /* Not quite the same as FICS suicide! */
1661           v = VariantGiveaway;
1662           break;
1663         case 27:
1664           v = VariantAtomic;
1665           break;
1666         case 28:
1667           v = VariantShatranj;
1668           break;
1669
1670         /* Temporary names for future ICC types.  The name *will* change in 
1671            the next xboard/WinBoard release after ICC defines it. */
1672         case 29:
1673           v = Variant29;
1674           break;
1675         case 30:
1676           v = Variant30;
1677           break;
1678         case 31:
1679           v = Variant31;
1680           break;
1681         case 32:
1682           v = Variant32;
1683           break;
1684         case 33:
1685           v = Variant33;
1686           break;
1687         case 34:
1688           v = Variant34;
1689           break;
1690         case 35:
1691           v = Variant35;
1692           break;
1693         case 36:
1694           v = Variant36;
1695           break;
1696         case 37:
1697           v = VariantShogi;
1698           break;
1699         case 38:
1700           v = VariantXiangqi;
1701           break;
1702         case 39:
1703           v = VariantCourier;
1704           break;
1705         case 40:
1706           v = VariantGothic;
1707           break;
1708         case 41:
1709           v = VariantCapablanca;
1710           break;
1711         case 42:
1712           v = VariantKnightmate;
1713           break;
1714         case 43:
1715           v = VariantFairy;
1716           break;
1717         case 44:
1718           v = VariantCylinder;
1719           break;
1720         case 45:
1721           v = VariantFalcon;
1722           break;
1723         case 46:
1724           v = VariantCapaRandom;
1725           break;
1726         case 47:
1727           v = VariantBerolina;
1728           break;
1729         case 48:
1730           v = VariantJanus;
1731           break;
1732         case 49:
1733           v = VariantSuper;
1734           break;
1735         case 50:
1736           v = VariantGreat;
1737           break;
1738         case -1:
1739           /* Found "wild" or "w" in the string but no number;
1740              must assume it's normal chess. */
1741           v = VariantNormal;
1742           break;
1743         default:
1744           sprintf(buf, _("Unknown wild type %d"), wnum);
1745           DisplayError(buf, 0);
1746           v = VariantUnknown;
1747           break;
1748         }
1749       }
1750     }
1751     if (appData.debugMode) {
1752       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1753               e, wnum, VariantName(v));
1754     }
1755     return v;
1756 }
1757
1758 static int leftover_start = 0, leftover_len = 0;
1759 char star_match[STAR_MATCH_N][MSG_SIZ];
1760
1761 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1762    advance *index beyond it, and set leftover_start to the new value of
1763    *index; else return FALSE.  If pattern contains the character '*', it
1764    matches any sequence of characters not containing '\r', '\n', or the
1765    character following the '*' (if any), and the matched sequence(s) are
1766    copied into star_match.
1767    */
1768 int
1769 looking_at(buf, index, pattern)
1770      char *buf;
1771      int *index;
1772      char *pattern;
1773 {
1774     char *bufp = &buf[*index], *patternp = pattern;
1775     int star_count = 0;
1776     char *matchp = star_match[0];
1777     
1778     for (;;) {
1779         if (*patternp == NULLCHAR) {
1780             *index = leftover_start = bufp - buf;
1781             *matchp = NULLCHAR;
1782             return TRUE;
1783         }
1784         if (*bufp == NULLCHAR) return FALSE;
1785         if (*patternp == '*') {
1786             if (*bufp == *(patternp + 1)) {
1787                 *matchp = NULLCHAR;
1788                 matchp = star_match[++star_count];
1789                 patternp += 2;
1790                 bufp++;
1791                 continue;
1792             } else if (*bufp == '\n' || *bufp == '\r') {
1793                 patternp++;
1794                 if (*patternp == NULLCHAR)
1795                   continue;
1796                 else
1797                   return FALSE;
1798             } else {
1799                 *matchp++ = *bufp++;
1800                 continue;
1801             }
1802         }
1803         if (*patternp != *bufp) return FALSE;
1804         patternp++;
1805         bufp++;
1806     }
1807 }
1808
1809 void
1810 SendToPlayer(data, length)
1811      char *data;
1812      int length;
1813 {
1814     int error, outCount;
1815     outCount = OutputToProcess(NoProc, data, length, &error);
1816     if (outCount < length) {
1817         DisplayFatalError(_("Error writing to display"), error, 1);
1818     }
1819 }
1820
1821 void
1822 PackHolding(packed, holding)
1823      char packed[];
1824      char *holding;
1825 {
1826     char *p = holding;
1827     char *q = packed;
1828     int runlength = 0;
1829     int curr = 9999;
1830     do {
1831         if (*p == curr) {
1832             runlength++;
1833         } else {
1834             switch (runlength) {
1835               case 0:
1836                 break;
1837               case 1:
1838                 *q++ = curr;
1839                 break;
1840               case 2:
1841                 *q++ = curr;
1842                 *q++ = curr;
1843                 break;
1844               default:
1845                 sprintf(q, "%d", runlength);
1846                 while (*q) q++;
1847                 *q++ = curr;
1848                 break;
1849             }
1850             runlength = 1;
1851             curr = *p;
1852         }
1853     } while (*p++);
1854     *q = NULLCHAR;
1855 }
1856
1857 /* Telnet protocol requests from the front end */
1858 void
1859 TelnetRequest(ddww, option)
1860      unsigned char ddww, option;
1861 {
1862     unsigned char msg[3];
1863     int outCount, outError;
1864
1865     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1866
1867     if (appData.debugMode) {
1868         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1869         switch (ddww) {
1870           case TN_DO:
1871             ddwwStr = "DO";
1872             break;
1873           case TN_DONT:
1874             ddwwStr = "DONT";
1875             break;
1876           case TN_WILL:
1877             ddwwStr = "WILL";
1878             break;
1879           case TN_WONT:
1880             ddwwStr = "WONT";
1881             break;
1882           default:
1883             ddwwStr = buf1;
1884             sprintf(buf1, "%d", ddww);
1885             break;
1886         }
1887         switch (option) {
1888           case TN_ECHO:
1889             optionStr = "ECHO";
1890             break;
1891           default:
1892             optionStr = buf2;
1893             sprintf(buf2, "%d", option);
1894             break;
1895         }
1896         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1897     }
1898     msg[0] = TN_IAC;
1899     msg[1] = ddww;
1900     msg[2] = option;
1901     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1902     if (outCount < 3) {
1903         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1904     }
1905 }
1906
1907 void
1908 DoEcho()
1909 {
1910     if (!appData.icsActive) return;
1911     TelnetRequest(TN_DO, TN_ECHO);
1912 }
1913
1914 void
1915 DontEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DONT, TN_ECHO);
1919 }
1920
1921 void
1922 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1923 {
1924     /* put the holdings sent to us by the server on the board holdings area */
1925     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1926     char p;
1927     ChessSquare piece;
1928
1929     if(gameInfo.holdingsWidth < 2)  return;
1930     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1931         return; // prevent overwriting by pre-board holdings
1932
1933     if( (int)lowestPiece >= BlackPawn ) {
1934         holdingsColumn = 0;
1935         countsColumn = 1;
1936         holdingsStartRow = BOARD_HEIGHT-1;
1937         direction = -1;
1938     } else {
1939         holdingsColumn = BOARD_WIDTH-1;
1940         countsColumn = BOARD_WIDTH-2;
1941         holdingsStartRow = 0;
1942         direction = 1;
1943     }
1944
1945     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1946         board[i][holdingsColumn] = EmptySquare;
1947         board[i][countsColumn]   = (ChessSquare) 0;
1948     }
1949     while( (p=*holdings++) != NULLCHAR ) {
1950         piece = CharToPiece( ToUpper(p) );
1951         if(piece == EmptySquare) continue;
1952         /*j = (int) piece - (int) WhitePawn;*/
1953         j = PieceToNumber(piece);
1954         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1955         if(j < 0) continue;               /* should not happen */
1956         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1957         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1958         board[holdingsStartRow+j*direction][countsColumn]++;
1959     }
1960 }
1961
1962
1963 void
1964 VariantSwitch(Board board, VariantClass newVariant)
1965 {
1966    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967    Board oldBoard;
1968
1969    startedFromPositionFile = FALSE;
1970    if(gameInfo.variant == newVariant) return;
1971
1972    /* [HGM] This routine is called each time an assignment is made to
1973     * gameInfo.variant during a game, to make sure the board sizes
1974     * are set to match the new variant. If that means adding or deleting
1975     * holdings, we shift the playing board accordingly
1976     * This kludge is needed because in ICS observe mode, we get boards
1977     * of an ongoing game without knowing the variant, and learn about the
1978     * latter only later. This can be because of the move list we requested,
1979     * in which case the game history is refilled from the beginning anyway,
1980     * but also when receiving holdings of a crazyhouse game. In the latter
1981     * case we want to add those holdings to the already received position.
1982     */
1983
1984    
1985    if (appData.debugMode) {
1986      fprintf(debugFP, "Switch board from %s to %s\n",
1987              VariantName(gameInfo.variant), VariantName(newVariant));
1988      setbuf(debugFP, NULL);
1989    }
1990    shuffleOpenings = 0;       /* [HGM] shuffle */
1991    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1992    switch(newVariant) 
1993      {
1994      case VariantShogi:
1995        newWidth = 9;  newHeight = 9;
1996        gameInfo.holdingsSize = 7;
1997      case VariantBughouse:
1998      case VariantCrazyhouse:
1999        newHoldingsWidth = 2; break;
2000      case VariantGreat:
2001        newWidth = 10;
2002      case VariantSuper:
2003        newHoldingsWidth = 2;
2004        gameInfo.holdingsSize = 8;
2005        break;
2006      case VariantGothic:
2007      case VariantCapablanca:
2008      case VariantCapaRandom:
2009        newWidth = 10;
2010      default:
2011        newHoldingsWidth = gameInfo.holdingsSize = 0;
2012      };
2013    
2014    if(newWidth  != gameInfo.boardWidth  ||
2015       newHeight != gameInfo.boardHeight ||
2016       newHoldingsWidth != gameInfo.holdingsWidth ) {
2017      
2018      /* shift position to new playing area, if needed */
2019      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2020        for(i=0; i<BOARD_HEIGHT; i++) 
2021          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2022            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2023              board[i][j];
2024        for(i=0; i<newHeight; i++) {
2025          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2026          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2027        }
2028      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2029        for(i=0; i<BOARD_HEIGHT; i++)
2030          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2031            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2032              board[i][j];
2033      }
2034      gameInfo.boardWidth  = newWidth;
2035      gameInfo.boardHeight = newHeight;
2036      gameInfo.holdingsWidth = newHoldingsWidth;
2037      gameInfo.variant = newVariant;
2038      InitDrawingSizes(-2, 0);
2039    } else gameInfo.variant = newVariant;
2040    CopyBoard(oldBoard, board);   // remember correctly formatted board
2041      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2042    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2043 }
2044
2045 static int loggedOn = FALSE;
2046
2047 /*-- Game start info cache: --*/
2048 int gs_gamenum;
2049 char gs_kind[MSG_SIZ];
2050 static char player1Name[128] = "";
2051 static char player2Name[128] = "";
2052 static char cont_seq[] = "\n\\   ";
2053 static int player1Rating = -1;
2054 static int player2Rating = -1;
2055 /*----------------------------*/
2056
2057 ColorClass curColor = ColorNormal;
2058 int suppressKibitz = 0;
2059
2060 void
2061 read_from_ics(isr, closure, data, count, error)
2062      InputSourceRef isr;
2063      VOIDSTAR closure;
2064      char *data;
2065      int count;
2066      int error;
2067 {
2068 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2069 #define STARTED_NONE 0
2070 #define STARTED_MOVES 1
2071 #define STARTED_BOARD 2
2072 #define STARTED_OBSERVE 3
2073 #define STARTED_HOLDINGS 4
2074 #define STARTED_CHATTER 5
2075 #define STARTED_COMMENT 6
2076 #define STARTED_MOVES_NOHIDE 7
2077     
2078     static int started = STARTED_NONE;
2079     static char parse[20000];
2080     static int parse_pos = 0;
2081     static char buf[BUF_SIZE + 1];
2082     static int firstTime = TRUE, intfSet = FALSE;
2083     static ColorClass prevColor = ColorNormal;
2084     static int savingComment = FALSE;
2085     static int cmatch = 0; // continuation sequence match
2086     char *bp;
2087     char str[500];
2088     int i, oldi;
2089     int buf_len;
2090     int next_out;
2091     int tkind;
2092     int backup;    /* [DM] For zippy color lines */
2093     char *p;
2094     char talker[MSG_SIZ]; // [HGM] chat
2095     int channel;
2096
2097     if (appData.debugMode) {
2098       if (!error) {
2099         fprintf(debugFP, "<ICS: ");
2100         show_bytes(debugFP, data, count);
2101         fprintf(debugFP, "\n");
2102       }
2103     }
2104
2105     if (appData.debugMode) { int f = forwardMostMove;
2106         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2107                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2108                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2109     }
2110     if (count > 0) {
2111         /* If last read ended with a partial line that we couldn't parse,
2112            prepend it to the new read and try again. */
2113         if (leftover_len > 0) {
2114             for (i=0; i<leftover_len; i++)
2115               buf[i] = buf[leftover_start + i];
2116         }
2117
2118     /* copy new characters into the buffer */
2119     bp = buf + leftover_len;
2120     buf_len=leftover_len;
2121     for (i=0; i<count; i++)
2122     {
2123         // ignore these
2124         if (data[i] == '\r')
2125             continue;
2126
2127         // join lines split by ICS?
2128         if (!appData.noJoin)
2129         {
2130             /*
2131                 Joining just consists of finding matches against the
2132                 continuation sequence, and discarding that sequence
2133                 if found instead of copying it.  So, until a match
2134                 fails, there's nothing to do since it might be the
2135                 complete sequence, and thus, something we don't want
2136                 copied.
2137             */
2138             if (data[i] == cont_seq[cmatch])
2139             {
2140                 cmatch++;
2141                 if (cmatch == strlen(cont_seq))
2142                 {
2143                     cmatch = 0; // complete match.  just reset the counter
2144
2145                     /*
2146                         it's possible for the ICS to not include the space
2147                         at the end of the last word, making our [correct]
2148                         join operation fuse two separate words.  the server
2149                         does this when the space occurs at the width setting.
2150                     */
2151                     if (!buf_len || buf[buf_len-1] != ' ')
2152                     {
2153                         *bp++ = ' ';
2154                         buf_len++;
2155                     }
2156                 }
2157                 continue;
2158             }
2159             else if (cmatch)
2160             {
2161                 /*
2162                     match failed, so we have to copy what matched before
2163                     falling through and copying this character.  In reality,
2164                     this will only ever be just the newline character, but
2165                     it doesn't hurt to be precise.
2166                 */
2167                 strncpy(bp, cont_seq, cmatch);
2168                 bp += cmatch;
2169                 buf_len += cmatch;
2170                 cmatch = 0;
2171             }
2172         }
2173
2174         // copy this char
2175         *bp++ = data[i];
2176         buf_len++;
2177     }
2178
2179         buf[buf_len] = NULLCHAR;
2180 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2181         next_out = 0;
2182         leftover_start = 0;
2183         
2184         i = 0;
2185         while (i < buf_len) {
2186             /* Deal with part of the TELNET option negotiation
2187                protocol.  We refuse to do anything beyond the
2188                defaults, except that we allow the WILL ECHO option,
2189                which ICS uses to turn off password echoing when we are
2190                directly connected to it.  We reject this option
2191                if localLineEditing mode is on (always on in xboard)
2192                and we are talking to port 23, which might be a real
2193                telnet server that will try to keep WILL ECHO on permanently.
2194              */
2195             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2196                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2197                 unsigned char option;
2198                 oldi = i;
2199                 switch ((unsigned char) buf[++i]) {
2200                   case TN_WILL:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<WILL ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       case TN_ECHO:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "ECHO ");
2207                         /* Reply only if this is a change, according
2208                            to the protocol rules. */
2209                         if (remoteEchoOption) break;
2210                         if (appData.localLineEditing &&
2211                             atoi(appData.icsPort) == TN_PORT) {
2212                             TelnetRequest(TN_DONT, TN_ECHO);
2213                         } else {
2214                             EchoOff();
2215                             TelnetRequest(TN_DO, TN_ECHO);
2216                             remoteEchoOption = TRUE;
2217                         }
2218                         break;
2219                       default:
2220                         if (appData.debugMode)
2221                           fprintf(debugFP, "%d ", option);
2222                         /* Whatever this is, we don't want it. */
2223                         TelnetRequest(TN_DONT, option);
2224                         break;
2225                     }
2226                     break;
2227                   case TN_WONT:
2228                     if (appData.debugMode)
2229                       fprintf(debugFP, "\n<WONT ");
2230                     switch (option = (unsigned char) buf[++i]) {
2231                       case TN_ECHO:
2232                         if (appData.debugMode)
2233                           fprintf(debugFP, "ECHO ");
2234                         /* Reply only if this is a change, according
2235                            to the protocol rules. */
2236                         if (!remoteEchoOption) break;
2237                         EchoOn();
2238                         TelnetRequest(TN_DONT, TN_ECHO);
2239                         remoteEchoOption = FALSE;
2240                         break;
2241                       default:
2242                         if (appData.debugMode)
2243                           fprintf(debugFP, "%d ", (unsigned char) option);
2244                         /* Whatever this is, it must already be turned
2245                            off, because we never agree to turn on
2246                            anything non-default, so according to the
2247                            protocol rules, we don't reply. */
2248                         break;
2249                     }
2250                     break;
2251                   case TN_DO:
2252                     if (appData.debugMode)
2253                       fprintf(debugFP, "\n<DO ");
2254                     switch (option = (unsigned char) buf[++i]) {
2255                       default:
2256                         /* Whatever this is, we refuse to do it. */
2257                         if (appData.debugMode)
2258                           fprintf(debugFP, "%d ", option);
2259                         TelnetRequest(TN_WONT, option);
2260                         break;
2261                     }
2262                     break;
2263                   case TN_DONT:
2264                     if (appData.debugMode)
2265                       fprintf(debugFP, "\n<DONT ");
2266                     switch (option = (unsigned char) buf[++i]) {
2267                       default:
2268                         if (appData.debugMode)
2269                           fprintf(debugFP, "%d ", option);
2270                         /* Whatever this is, we are already not doing
2271                            it, because we never agree to do anything
2272                            non-default, so according to the protocol
2273                            rules, we don't reply. */
2274                         break;
2275                     }
2276                     break;
2277                   case TN_IAC:
2278                     if (appData.debugMode)
2279                       fprintf(debugFP, "\n<IAC ");
2280                     /* Doubled IAC; pass it through */
2281                     i--;
2282                     break;
2283                   default:
2284                     if (appData.debugMode)
2285                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2286                     /* Drop all other telnet commands on the floor */
2287                     break;
2288                 }
2289                 if (oldi > next_out)
2290                   SendToPlayer(&buf[next_out], oldi - next_out);
2291                 if (++i > next_out)
2292                   next_out = i;
2293                 continue;
2294             }
2295                 
2296             /* OK, this at least will *usually* work */
2297             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2298                 loggedOn = TRUE;
2299             }
2300             
2301             if (loggedOn && !intfSet) {
2302                 if (ics_type == ICS_ICC) {
2303                   sprintf(str,
2304                           "/set-quietly interface %s\n/set-quietly style 12\n",
2305                           programVersion);
2306                 } else if (ics_type == ICS_CHESSNET) {
2307                   sprintf(str, "/style 12\n");
2308                 } else {
2309                   strcpy(str, "alias $ @\n$set interface ");
2310                   strcat(str, programVersion);
2311                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2312 #ifdef WIN32
2313                   strcat(str, "$iset nohighlight 1\n");
2314 #endif
2315                   strcat(str, "$iset lock 1\n$style 12\n");
2316                 }
2317                 SendToICS(str);
2318                 NotifyFrontendLogin();
2319                 intfSet = TRUE;
2320             }
2321
2322             if (started == STARTED_COMMENT) {
2323                 /* Accumulate characters in comment */
2324                 parse[parse_pos++] = buf[i];
2325                 if (buf[i] == '\n') {
2326                     parse[parse_pos] = NULLCHAR;
2327                     if(chattingPartner>=0) {
2328                         char mess[MSG_SIZ];
2329                         sprintf(mess, "%s%s", talker, parse);
2330                         OutputChatMessage(chattingPartner, mess);
2331                         chattingPartner = -1;
2332                     } else
2333                     if(!suppressKibitz) // [HGM] kibitz
2334                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2335                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2336                         int nrDigit = 0, nrAlph = 0, j;
2337                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2338                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2339                         parse[parse_pos] = NULLCHAR;
2340                         // try to be smart: if it does not look like search info, it should go to
2341                         // ICS interaction window after all, not to engine-output window.
2342                         for(j=0; j<parse_pos; j++) { // count letters and digits
2343                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2344                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2345                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2346                         }
2347                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2348                             int depth=0; float score;
2349                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2350                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2351                                 pvInfoList[forwardMostMove-1].depth = depth;
2352                                 pvInfoList[forwardMostMove-1].score = 100*score;
2353                             }
2354                             OutputKibitz(suppressKibitz, parse);
2355                             next_out = i+1; // [HGM] suppress printing in ICS window
2356                         } else {
2357                             char tmp[MSG_SIZ];
2358                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2359                             SendToPlayer(tmp, strlen(tmp));
2360                         }
2361                     }
2362                     started = STARTED_NONE;
2363                 } else {
2364                     /* Don't match patterns against characters in comment */
2365                     i++;
2366                     continue;
2367                 }
2368             }
2369             if (started == STARTED_CHATTER) {
2370                 if (buf[i] != '\n') {
2371                     /* Don't match patterns against characters in chatter */
2372                     i++;
2373                     continue;
2374                 }
2375                 started = STARTED_NONE;
2376             }
2377
2378             /* Kludge to deal with rcmd protocol */
2379             if (firstTime && looking_at(buf, &i, "\001*")) {
2380                 DisplayFatalError(&buf[1], 0, 1);
2381                 continue;
2382             } else {
2383                 firstTime = FALSE;
2384             }
2385
2386             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2387                 ics_type = ICS_ICC;
2388                 ics_prefix = "/";
2389                 if (appData.debugMode)
2390                   fprintf(debugFP, "ics_type %d\n", ics_type);
2391                 continue;
2392             }
2393             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2394                 ics_type = ICS_FICS;
2395                 ics_prefix = "$";
2396                 if (appData.debugMode)
2397                   fprintf(debugFP, "ics_type %d\n", ics_type);
2398                 continue;
2399             }
2400             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2401                 ics_type = ICS_CHESSNET;
2402                 ics_prefix = "/";
2403                 if (appData.debugMode)
2404                   fprintf(debugFP, "ics_type %d\n", ics_type);
2405                 continue;
2406             }
2407
2408             if (!loggedOn &&
2409                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2410                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2411                  looking_at(buf, &i, "will be \"*\""))) {
2412               strcpy(ics_handle, star_match[0]);
2413               continue;
2414             }
2415
2416             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2417               char buf[MSG_SIZ];
2418               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2419               DisplayIcsInteractionTitle(buf);
2420               have_set_title = TRUE;
2421             }
2422
2423             /* skip finger notes */
2424             if (started == STARTED_NONE &&
2425                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2426                  (buf[i] == '1' && buf[i+1] == '0')) &&
2427                 buf[i+2] == ':' && buf[i+3] == ' ') {
2428               started = STARTED_CHATTER;
2429               i += 3;
2430               continue;
2431             }
2432
2433             /* skip formula vars */
2434             if (started == STARTED_NONE &&
2435                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2436               started = STARTED_CHATTER;
2437               i += 3;
2438               continue;
2439             }
2440
2441             oldi = i;
2442             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2443             if (appData.autoKibitz && started == STARTED_NONE && 
2444                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2445                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2446                 if(looking_at(buf, &i, "* kibitzes: ") &&
2447                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2448                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2449                         suppressKibitz = TRUE;
2450                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2451                                 && (gameMode == IcsPlayingWhite)) ||
2452                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2453                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2454                             started = STARTED_CHATTER; // own kibitz we simply discard
2455                         else {
2456                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2457                             parse_pos = 0; parse[0] = NULLCHAR;
2458                             savingComment = TRUE;
2459                             suppressKibitz = gameMode != IcsObserving ? 2 :
2460                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2461                         } 
2462                         continue;
2463                 } else
2464                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2465                     // suppress the acknowledgements of our own autoKibitz
2466                     SendToPlayer(star_match[0], strlen(star_match[0]));
2467                     looking_at(buf, &i, "*% "); // eat prompt
2468                     next_out = i;
2469                 }
2470             } // [HGM] kibitz: end of patch
2471
2472 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2473
2474             // [HGM] chat: intercept tells by users for which we have an open chat window
2475             channel = -1;
2476             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2477                                            looking_at(buf, &i, "* whispers:") ||
2478                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2479                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2480                 int p;
2481                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2482                 chattingPartner = -1;
2483
2484                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2485                 for(p=0; p<MAX_CHAT; p++) {
2486                     if(channel == atoi(chatPartner[p])) {
2487                     talker[0] = '['; strcat(talker, "]");
2488                     chattingPartner = p; break;
2489                     }
2490                 } else
2491                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2492                 for(p=0; p<MAX_CHAT; p++) {
2493                     if(!strcmp("WHISPER", chatPartner[p])) {
2494                         talker[0] = '['; strcat(talker, "]");
2495                         chattingPartner = p; break;
2496                     }
2497                 }
2498                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2499                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2500                     talker[0] = 0;
2501                     chattingPartner = p; break;
2502                 }
2503                 if(chattingPartner<0) i = oldi; else {
2504                     started = STARTED_COMMENT;
2505                     parse_pos = 0; parse[0] = NULLCHAR;
2506                     savingComment = TRUE;
2507                     suppressKibitz = TRUE;
2508                 }
2509             } // [HGM] chat: end of patch
2510
2511             if (appData.zippyTalk || appData.zippyPlay) {
2512                 /* [DM] Backup address for color zippy lines */
2513                 backup = i;
2514 #if ZIPPY
2515        #ifdef WIN32
2516                if (loggedOn == TRUE)
2517                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2518                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2519        #else
2520                 if (ZippyControl(buf, &i) ||
2521                     ZippyConverse(buf, &i) ||
2522                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2523                       loggedOn = TRUE;
2524                       if (!appData.colorize) continue;
2525                 }
2526        #endif
2527 #endif
2528             } // [DM] 'else { ' deleted
2529                 if (
2530                     /* Regular tells and says */
2531                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2532                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2533                     looking_at(buf, &i, "* says: ") ||
2534                     /* Don't color "message" or "messages" output */
2535                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2536                     looking_at(buf, &i, "*. * at *:*: ") ||
2537                     looking_at(buf, &i, "--* (*:*): ") ||
2538                     /* Message notifications (same color as tells) */
2539                     looking_at(buf, &i, "* has left a message ") ||
2540                     looking_at(buf, &i, "* just sent you a message:\n") ||
2541                     /* Whispers and kibitzes */
2542                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2543                     looking_at(buf, &i, "* kibitzes: ") ||
2544                     /* Channel tells */
2545                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2546
2547                   if (tkind == 1 && strchr(star_match[0], ':')) {
2548                       /* Avoid "tells you:" spoofs in channels */
2549                      tkind = 3;
2550                   }
2551                   if (star_match[0][0] == NULLCHAR ||
2552                       strchr(star_match[0], ' ') ||
2553                       (tkind == 3 && strchr(star_match[1], ' '))) {
2554                     /* Reject bogus matches */
2555                     i = oldi;
2556                   } else {
2557                     if (appData.colorize) {
2558                       if (oldi > next_out) {
2559                         SendToPlayer(&buf[next_out], oldi - next_out);
2560                         next_out = oldi;
2561                       }
2562                       switch (tkind) {
2563                       case 1:
2564                         Colorize(ColorTell, FALSE);
2565                         curColor = ColorTell;
2566                         break;
2567                       case 2:
2568                         Colorize(ColorKibitz, FALSE);
2569                         curColor = ColorKibitz;
2570                         break;
2571                       case 3:
2572                         p = strrchr(star_match[1], '(');
2573                         if (p == NULL) {
2574                           p = star_match[1];
2575                         } else {
2576                           p++;
2577                         }
2578                         if (atoi(p) == 1) {
2579                           Colorize(ColorChannel1, FALSE);
2580                           curColor = ColorChannel1;
2581                         } else {
2582                           Colorize(ColorChannel, FALSE);
2583                           curColor = ColorChannel;
2584                         }
2585                         break;
2586                       case 5:
2587                         curColor = ColorNormal;
2588                         break;
2589                       }
2590                     }
2591                     if (started == STARTED_NONE && appData.autoComment &&
2592                         (gameMode == IcsObserving ||
2593                          gameMode == IcsPlayingWhite ||
2594                          gameMode == IcsPlayingBlack)) {
2595                       parse_pos = i - oldi;
2596                       memcpy(parse, &buf[oldi], parse_pos);
2597                       parse[parse_pos] = NULLCHAR;
2598                       started = STARTED_COMMENT;
2599                       savingComment = TRUE;
2600                     } else {
2601                       started = STARTED_CHATTER;
2602                       savingComment = FALSE;
2603                     }
2604                     loggedOn = TRUE;
2605                     continue;
2606                   }
2607                 }
2608
2609                 if (looking_at(buf, &i, "* s-shouts: ") ||
2610                     looking_at(buf, &i, "* c-shouts: ")) {
2611                     if (appData.colorize) {
2612                         if (oldi > next_out) {
2613                             SendToPlayer(&buf[next_out], oldi - next_out);
2614                             next_out = oldi;
2615                         }
2616                         Colorize(ColorSShout, FALSE);
2617                         curColor = ColorSShout;
2618                     }
2619                     loggedOn = TRUE;
2620                     started = STARTED_CHATTER;
2621                     continue;
2622                 }
2623
2624                 if (looking_at(buf, &i, "--->")) {
2625                     loggedOn = TRUE;
2626                     continue;
2627                 }
2628
2629                 if (looking_at(buf, &i, "* shouts: ") ||
2630                     looking_at(buf, &i, "--> ")) {
2631                     if (appData.colorize) {
2632                         if (oldi > next_out) {
2633                             SendToPlayer(&buf[next_out], oldi - next_out);
2634                             next_out = oldi;
2635                         }
2636                         Colorize(ColorShout, FALSE);
2637                         curColor = ColorShout;
2638                     }
2639                     loggedOn = TRUE;
2640                     started = STARTED_CHATTER;
2641                     continue;
2642                 }
2643
2644                 if (looking_at( buf, &i, "Challenge:")) {
2645                     if (appData.colorize) {
2646                         if (oldi > next_out) {
2647                             SendToPlayer(&buf[next_out], oldi - next_out);
2648                             next_out = oldi;
2649                         }
2650                         Colorize(ColorChallenge, FALSE);
2651                         curColor = ColorChallenge;
2652                     }
2653                     loggedOn = TRUE;
2654                     continue;
2655                 }
2656
2657                 if (looking_at(buf, &i, "* offers you") ||
2658                     looking_at(buf, &i, "* offers to be") ||
2659                     looking_at(buf, &i, "* would like to") ||
2660                     looking_at(buf, &i, "* requests to") ||
2661                     looking_at(buf, &i, "Your opponent offers") ||
2662                     looking_at(buf, &i, "Your opponent requests")) {
2663
2664                     if (appData.colorize) {
2665                         if (oldi > next_out) {
2666                             SendToPlayer(&buf[next_out], oldi - next_out);
2667                             next_out = oldi;
2668                         }
2669                         Colorize(ColorRequest, FALSE);
2670                         curColor = ColorRequest;
2671                     }
2672                     continue;
2673                 }
2674
2675                 if (looking_at(buf, &i, "* (*) seeking")) {
2676                     if (appData.colorize) {
2677                         if (oldi > next_out) {
2678                             SendToPlayer(&buf[next_out], oldi - next_out);
2679                             next_out = oldi;
2680                         }
2681                         Colorize(ColorSeek, FALSE);
2682                         curColor = ColorSeek;
2683                     }
2684                     continue;
2685             }
2686
2687             if (looking_at(buf, &i, "\\   ")) {
2688                 if (prevColor != ColorNormal) {
2689                     if (oldi > next_out) {
2690                         SendToPlayer(&buf[next_out], oldi - next_out);
2691                         next_out = oldi;
2692                     }
2693                     Colorize(prevColor, TRUE);
2694                     curColor = prevColor;
2695                 }
2696                 if (savingComment) {
2697                     parse_pos = i - oldi;
2698                     memcpy(parse, &buf[oldi], parse_pos);
2699                     parse[parse_pos] = NULLCHAR;
2700                     started = STARTED_COMMENT;
2701                 } else {
2702                     started = STARTED_CHATTER;
2703                 }
2704                 continue;
2705             }
2706
2707             if (looking_at(buf, &i, "Black Strength :") ||
2708                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2709                 looking_at(buf, &i, "<10>") ||
2710                 looking_at(buf, &i, "#@#")) {
2711                 /* Wrong board style */
2712                 loggedOn = TRUE;
2713                 SendToICS(ics_prefix);
2714                 SendToICS("set style 12\n");
2715                 SendToICS(ics_prefix);
2716                 SendToICS("refresh\n");
2717                 continue;
2718             }
2719             
2720             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2721                 ICSInitScript();
2722                 have_sent_ICS_logon = 1;
2723                 continue;
2724             }
2725               
2726             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2727                 (looking_at(buf, &i, "\n<12> ") ||
2728                  looking_at(buf, &i, "<12> "))) {
2729                 loggedOn = TRUE;
2730                 if (oldi > next_out) {
2731                     SendToPlayer(&buf[next_out], oldi - next_out);
2732                 }
2733                 next_out = i;
2734                 started = STARTED_BOARD;
2735                 parse_pos = 0;
2736                 continue;
2737             }
2738
2739             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2740                 looking_at(buf, &i, "<b1> ")) {
2741                 if (oldi > next_out) {
2742                     SendToPlayer(&buf[next_out], oldi - next_out);
2743                 }
2744                 next_out = i;
2745                 started = STARTED_HOLDINGS;
2746                 parse_pos = 0;
2747                 continue;
2748             }
2749
2750             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2751                 loggedOn = TRUE;
2752                 /* Header for a move list -- first line */
2753
2754                 switch (ics_getting_history) {
2755                   case H_FALSE:
2756                     switch (gameMode) {
2757                       case IcsIdle:
2758                       case BeginningOfGame:
2759                         /* User typed "moves" or "oldmoves" while we
2760                            were idle.  Pretend we asked for these
2761                            moves and soak them up so user can step
2762                            through them and/or save them.
2763                            */
2764                         Reset(FALSE, TRUE);
2765                         gameMode = IcsObserving;
2766                         ModeHighlight();
2767                         ics_gamenum = -1;
2768                         ics_getting_history = H_GOT_UNREQ_HEADER;
2769                         break;
2770                       case EditGame: /*?*/
2771                       case EditPosition: /*?*/
2772                         /* Should above feature work in these modes too? */
2773                         /* For now it doesn't */
2774                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2775                         break;
2776                       default:
2777                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2778                         break;
2779                     }
2780                     break;
2781                   case H_REQUESTED:
2782                     /* Is this the right one? */
2783                     if (gameInfo.white && gameInfo.black &&
2784                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2785                         strcmp(gameInfo.black, star_match[2]) == 0) {
2786                         /* All is well */
2787                         ics_getting_history = H_GOT_REQ_HEADER;
2788                     }
2789                     break;
2790                   case H_GOT_REQ_HEADER:
2791                   case H_GOT_UNREQ_HEADER:
2792                   case H_GOT_UNWANTED_HEADER:
2793                   case H_GETTING_MOVES:
2794                     /* Should not happen */
2795                     DisplayError(_("Error gathering move list: two headers"), 0);
2796                     ics_getting_history = H_FALSE;
2797                     break;
2798                 }
2799
2800                 /* Save player ratings into gameInfo if needed */
2801                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2802                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2803                     (gameInfo.whiteRating == -1 ||
2804                      gameInfo.blackRating == -1)) {
2805
2806                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2807                     gameInfo.blackRating = string_to_rating(star_match[3]);
2808                     if (appData.debugMode)
2809                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2810                               gameInfo.whiteRating, gameInfo.blackRating);
2811                 }
2812                 continue;
2813             }
2814
2815             if (looking_at(buf, &i,
2816               "* * match, initial time: * minute*, increment: * second")) {
2817                 /* Header for a move list -- second line */
2818                 /* Initial board will follow if this is a wild game */
2819                 if (gameInfo.event != NULL) free(gameInfo.event);
2820                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2821                 gameInfo.event = StrSave(str);
2822                 /* [HGM] we switched variant. Translate boards if needed. */
2823                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2824                 continue;
2825             }
2826
2827             if (looking_at(buf, &i, "Move  ")) {
2828                 /* Beginning of a move list */
2829                 switch (ics_getting_history) {
2830                   case H_FALSE:
2831                     /* Normally should not happen */
2832                     /* Maybe user hit reset while we were parsing */
2833                     break;
2834                   case H_REQUESTED:
2835                     /* Happens if we are ignoring a move list that is not
2836                      * the one we just requested.  Common if the user
2837                      * tries to observe two games without turning off
2838                      * getMoveList */
2839                     break;
2840                   case H_GETTING_MOVES:
2841                     /* Should not happen */
2842                     DisplayError(_("Error gathering move list: nested"), 0);
2843                     ics_getting_history = H_FALSE;
2844                     break;
2845                   case H_GOT_REQ_HEADER:
2846                     ics_getting_history = H_GETTING_MOVES;
2847                     started = STARTED_MOVES;
2848                     parse_pos = 0;
2849                     if (oldi > next_out) {
2850                         SendToPlayer(&buf[next_out], oldi - next_out);
2851                     }
2852                     break;
2853                   case H_GOT_UNREQ_HEADER:
2854                     ics_getting_history = H_GETTING_MOVES;
2855                     started = STARTED_MOVES_NOHIDE;
2856                     parse_pos = 0;
2857                     break;
2858                   case H_GOT_UNWANTED_HEADER:
2859                     ics_getting_history = H_FALSE;
2860                     break;
2861                 }
2862                 continue;
2863             }                           
2864             
2865             if (looking_at(buf, &i, "% ") ||
2866                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2867                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2868                 if(suppressKibitz) next_out = i;
2869                 savingComment = FALSE;
2870                 suppressKibitz = 0;
2871                 switch (started) {
2872                   case STARTED_MOVES:
2873                   case STARTED_MOVES_NOHIDE:
2874                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2875                     parse[parse_pos + i - oldi] = NULLCHAR;
2876                     ParseGameHistory(parse);
2877 #if ZIPPY
2878                     if (appData.zippyPlay && first.initDone) {
2879                         FeedMovesToProgram(&first, forwardMostMove);
2880                         if (gameMode == IcsPlayingWhite) {
2881                             if (WhiteOnMove(forwardMostMove)) {
2882                                 if (first.sendTime) {
2883                                   if (first.useColors) {
2884                                     SendToProgram("black\n", &first); 
2885                                   }
2886                                   SendTimeRemaining(&first, TRUE);
2887                                 }
2888                                 if (first.useColors) {
2889                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2890                                 }
2891                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2892                                 first.maybeThinking = TRUE;
2893                             } else {
2894                                 if (first.usePlayother) {
2895                                   if (first.sendTime) {
2896                                     SendTimeRemaining(&first, TRUE);
2897                                   }
2898                                   SendToProgram("playother\n", &first);
2899                                   firstMove = FALSE;
2900                                 } else {
2901                                   firstMove = TRUE;
2902                                 }
2903                             }
2904                         } else if (gameMode == IcsPlayingBlack) {
2905                             if (!WhiteOnMove(forwardMostMove)) {
2906                                 if (first.sendTime) {
2907                                   if (first.useColors) {
2908                                     SendToProgram("white\n", &first);
2909                                   }
2910                                   SendTimeRemaining(&first, FALSE);
2911                                 }
2912                                 if (first.useColors) {
2913                                   SendToProgram("black\n", &first);
2914                                 }
2915                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2916                                 first.maybeThinking = TRUE;
2917                             } else {
2918                                 if (first.usePlayother) {
2919                                   if (first.sendTime) {
2920                                     SendTimeRemaining(&first, FALSE);
2921                                   }
2922                                   SendToProgram("playother\n", &first);
2923                                   firstMove = FALSE;
2924                                 } else {
2925                                   firstMove = TRUE;
2926                                 }
2927                             }
2928                         }                       
2929                     }
2930 #endif
2931                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2932                         /* Moves came from oldmoves or moves command
2933                            while we weren't doing anything else.
2934                            */
2935                         currentMove = forwardMostMove;
2936                         ClearHighlights();/*!!could figure this out*/
2937                         flipView = appData.flipView;
2938                         DrawPosition(TRUE, boards[currentMove]);
2939                         DisplayBothClocks();
2940                         sprintf(str, "%s vs. %s",
2941                                 gameInfo.white, gameInfo.black);
2942                         DisplayTitle(str);
2943                         gameMode = IcsIdle;
2944                     } else {
2945                         /* Moves were history of an active game */
2946                         if (gameInfo.resultDetails != NULL) {
2947                             free(gameInfo.resultDetails);
2948                             gameInfo.resultDetails = NULL;
2949                         }
2950                     }
2951                     HistorySet(parseList, backwardMostMove,
2952                                forwardMostMove, currentMove-1);
2953                     DisplayMove(currentMove - 1);
2954                     if (started == STARTED_MOVES) next_out = i;
2955                     started = STARTED_NONE;
2956                     ics_getting_history = H_FALSE;
2957                     break;
2958
2959                   case STARTED_OBSERVE:
2960                     started = STARTED_NONE;
2961                     SendToICS(ics_prefix);
2962                     SendToICS("refresh\n");
2963                     break;
2964
2965                   default:
2966                     break;
2967                 }
2968                 if(bookHit) { // [HGM] book: simulate book reply
2969                     static char bookMove[MSG_SIZ]; // a bit generous?
2970
2971                     programStats.nodes = programStats.depth = programStats.time = 
2972                     programStats.score = programStats.got_only_move = 0;
2973                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2974
2975                     strcpy(bookMove, "move ");
2976                     strcat(bookMove, bookHit);
2977                     HandleMachineMove(bookMove, &first);
2978                 }
2979                 continue;
2980             }
2981             
2982             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2983                  started == STARTED_HOLDINGS ||
2984                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2985                 /* Accumulate characters in move list or board */
2986                 parse[parse_pos++] = buf[i];
2987             }
2988             
2989             /* Start of game messages.  Mostly we detect start of game
2990                when the first board image arrives.  On some versions
2991                of the ICS, though, we need to do a "refresh" after starting
2992                to observe in order to get the current board right away. */
2993             if (looking_at(buf, &i, "Adding game * to observation list")) {
2994                 started = STARTED_OBSERVE;
2995                 continue;
2996             }
2997
2998             /* Handle auto-observe */
2999             if (appData.autoObserve &&
3000                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3001                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3002                 char *player;
3003                 /* Choose the player that was highlighted, if any. */
3004                 if (star_match[0][0] == '\033' ||
3005                     star_match[1][0] != '\033') {
3006                     player = star_match[0];
3007                 } else {
3008                     player = star_match[2];
3009                 }
3010                 sprintf(str, "%sobserve %s\n",
3011                         ics_prefix, StripHighlightAndTitle(player));
3012                 SendToICS(str);
3013
3014                 /* Save ratings from notify string */
3015                 strcpy(player1Name, star_match[0]);
3016                 player1Rating = string_to_rating(star_match[1]);
3017                 strcpy(player2Name, star_match[2]);
3018                 player2Rating = string_to_rating(star_match[3]);
3019
3020                 if (appData.debugMode)
3021                   fprintf(debugFP, 
3022                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3023                           player1Name, player1Rating,
3024                           player2Name, player2Rating);
3025
3026                 continue;
3027             }
3028
3029             /* Deal with automatic examine mode after a game,
3030                and with IcsObserving -> IcsExamining transition */
3031             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3032                 looking_at(buf, &i, "has made you an examiner of game *")) {
3033
3034                 int gamenum = atoi(star_match[0]);
3035                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3036                     gamenum == ics_gamenum) {
3037                     /* We were already playing or observing this game;
3038                        no need to refetch history */
3039                     gameMode = IcsExamining;
3040                     if (pausing) {
3041                         pauseExamForwardMostMove = forwardMostMove;
3042                     } else if (currentMove < forwardMostMove) {
3043                         ForwardInner(forwardMostMove);
3044                     }
3045                 } else {
3046                     /* I don't think this case really can happen */
3047                     SendToICS(ics_prefix);
3048                     SendToICS("refresh\n");
3049                 }
3050                 continue;
3051             }    
3052             
3053             /* Error messages */
3054 //          if (ics_user_moved) {
3055             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3056                 if (looking_at(buf, &i, "Illegal move") ||
3057                     looking_at(buf, &i, "Not a legal move") ||
3058                     looking_at(buf, &i, "Your king is in check") ||
3059                     looking_at(buf, &i, "It isn't your turn") ||
3060                     looking_at(buf, &i, "It is not your move")) {
3061                     /* Illegal move */
3062                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3063                         currentMove = --forwardMostMove;
3064                         DisplayMove(currentMove - 1); /* before DMError */
3065                         DrawPosition(FALSE, boards[currentMove]);
3066                         SwitchClocks();
3067                         DisplayBothClocks();
3068                     }
3069                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3070                     ics_user_moved = 0;
3071                     continue;
3072                 }
3073             }
3074
3075             if (looking_at(buf, &i, "still have time") ||
3076                 looking_at(buf, &i, "not out of time") ||
3077                 looking_at(buf, &i, "either player is out of time") ||
3078                 looking_at(buf, &i, "has timeseal; checking")) {
3079                 /* We must have called his flag a little too soon */
3080                 whiteFlag = blackFlag = FALSE;
3081                 continue;
3082             }
3083
3084             if (looking_at(buf, &i, "added * seconds to") ||
3085                 looking_at(buf, &i, "seconds were added to")) {
3086                 /* Update the clocks */
3087                 SendToICS(ics_prefix);
3088                 SendToICS("refresh\n");
3089                 continue;
3090             }
3091
3092             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3093                 ics_clock_paused = TRUE;
3094                 StopClocks();
3095                 continue;
3096             }
3097
3098             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3099                 ics_clock_paused = FALSE;
3100                 StartClocks();
3101                 continue;
3102             }
3103
3104             /* Grab player ratings from the Creating: message.
3105                Note we have to check for the special case when
3106                the ICS inserts things like [white] or [black]. */
3107             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3108                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3109                 /* star_matches:
3110                    0    player 1 name (not necessarily white)
3111                    1    player 1 rating
3112                    2    empty, white, or black (IGNORED)
3113                    3    player 2 name (not necessarily black)
3114                    4    player 2 rating
3115                    
3116                    The names/ratings are sorted out when the game
3117                    actually starts (below).
3118                 */
3119                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3120                 player1Rating = string_to_rating(star_match[1]);
3121                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3122                 player2Rating = string_to_rating(star_match[4]);
3123
3124                 if (appData.debugMode)
3125                   fprintf(debugFP, 
3126                           "Ratings from 'Creating:' %s %d, %s %d\n",
3127                           player1Name, player1Rating,
3128                           player2Name, player2Rating);
3129
3130                 continue;
3131             }
3132             
3133             /* Improved generic start/end-of-game messages */
3134             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3135                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3136                 /* If tkind == 0: */
3137                 /* star_match[0] is the game number */
3138                 /*           [1] is the white player's name */
3139                 /*           [2] is the black player's name */
3140                 /* For end-of-game: */
3141                 /*           [3] is the reason for the game end */
3142                 /*           [4] is a PGN end game-token, preceded by " " */
3143                 /* For start-of-game: */
3144                 /*           [3] begins with "Creating" or "Continuing" */
3145                 /*           [4] is " *" or empty (don't care). */
3146                 int gamenum = atoi(star_match[0]);
3147                 char *whitename, *blackname, *why, *endtoken;
3148                 ChessMove endtype = (ChessMove) 0;
3149
3150                 if (tkind == 0) {
3151                   whitename = star_match[1];
3152                   blackname = star_match[2];
3153                   why = star_match[3];
3154                   endtoken = star_match[4];
3155                 } else {
3156                   whitename = star_match[1];
3157                   blackname = star_match[3];
3158                   why = star_match[5];
3159                   endtoken = star_match[6];
3160                 }
3161
3162                 /* Game start messages */
3163                 if (strncmp(why, "Creating ", 9) == 0 ||
3164                     strncmp(why, "Continuing ", 11) == 0) {
3165                     gs_gamenum = gamenum;
3166                     strcpy(gs_kind, strchr(why, ' ') + 1);
3167                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3168 #if ZIPPY
3169                     if (appData.zippyPlay) {
3170                         ZippyGameStart(whitename, blackname);
3171                     }
3172 #endif /*ZIPPY*/
3173                     continue;
3174                 }
3175
3176                 /* Game end messages */
3177                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3178                     ics_gamenum != gamenum) {
3179                     continue;
3180                 }
3181                 while (endtoken[0] == ' ') endtoken++;
3182                 switch (endtoken[0]) {
3183                   case '*':
3184                   default:
3185                     endtype = GameUnfinished;
3186                     break;
3187                   case '0':
3188                     endtype = BlackWins;
3189                     break;
3190                   case '1':
3191                     if (endtoken[1] == '/')
3192                       endtype = GameIsDrawn;
3193                     else
3194                       endtype = WhiteWins;
3195                     break;
3196                 }
3197                 GameEnds(endtype, why, GE_ICS);
3198 #if ZIPPY
3199                 if (appData.zippyPlay && first.initDone) {
3200                     ZippyGameEnd(endtype, why);
3201                     if (first.pr == NULL) {
3202                       /* Start the next process early so that we'll
3203                          be ready for the next challenge */
3204                       StartChessProgram(&first);
3205                     }
3206                     /* Send "new" early, in case this command takes
3207                        a long time to finish, so that we'll be ready
3208                        for the next challenge. */
3209                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3210                     Reset(TRUE, TRUE);
3211                 }
3212 #endif /*ZIPPY*/
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i, "Removing game * from observation") ||
3217                 looking_at(buf, &i, "no longer observing game *") ||
3218                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3219                 if (gameMode == IcsObserving &&
3220                     atoi(star_match[0]) == ics_gamenum)
3221                   {
3222                       /* icsEngineAnalyze */
3223                       if (appData.icsEngineAnalyze) {
3224                             ExitAnalyzeMode();
3225                             ModeHighlight();
3226                       }
3227                       StopClocks();
3228                       gameMode = IcsIdle;
3229                       ics_gamenum = -1;
3230                       ics_user_moved = FALSE;
3231                   }
3232                 continue;
3233             }
3234
3235             if (looking_at(buf, &i, "no longer examining game *")) {
3236                 if (gameMode == IcsExamining &&
3237                     atoi(star_match[0]) == ics_gamenum)
3238                   {
3239                       gameMode = IcsIdle;
3240                       ics_gamenum = -1;
3241                       ics_user_moved = FALSE;
3242                   }
3243                 continue;
3244             }
3245
3246             /* Advance leftover_start past any newlines we find,
3247                so only partial lines can get reparsed */
3248             if (looking_at(buf, &i, "\n")) {
3249                 prevColor = curColor;
3250                 if (curColor != ColorNormal) {
3251                     if (oldi > next_out) {
3252                         SendToPlayer(&buf[next_out], oldi - next_out);
3253                         next_out = oldi;
3254                     }
3255                     Colorize(ColorNormal, FALSE);
3256                     curColor = ColorNormal;
3257                 }
3258                 if (started == STARTED_BOARD) {
3259                     started = STARTED_NONE;
3260                     parse[parse_pos] = NULLCHAR;
3261                     ParseBoard12(parse);
3262                     ics_user_moved = 0;
3263
3264                     /* Send premove here */
3265                     if (appData.premove) {
3266                       char str[MSG_SIZ];
3267                       if (currentMove == 0 &&
3268                           gameMode == IcsPlayingWhite &&
3269                           appData.premoveWhite) {
3270                         sprintf(str, "%s\n", appData.premoveWhiteText);
3271                         if (appData.debugMode)
3272                           fprintf(debugFP, "Sending premove:\n");
3273                         SendToICS(str);
3274                       } else if (currentMove == 1 &&
3275                                  gameMode == IcsPlayingBlack &&
3276                                  appData.premoveBlack) {
3277                         sprintf(str, "%s\n", appData.premoveBlackText);
3278                         if (appData.debugMode)
3279                           fprintf(debugFP, "Sending premove:\n");
3280                         SendToICS(str);
3281                       } else if (gotPremove) {
3282                         gotPremove = 0;
3283                         ClearPremoveHighlights();
3284                         if (appData.debugMode)
3285                           fprintf(debugFP, "Sending premove:\n");
3286                           UserMoveEvent(premoveFromX, premoveFromY, 
3287                                         premoveToX, premoveToY, 
3288                                         premovePromoChar);
3289                       }
3290                     }
3291
3292                     /* Usually suppress following prompt */
3293                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3294                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3295                         if (looking_at(buf, &i, "*% ")) {
3296                             savingComment = FALSE;
3297                             suppressKibitz = 0;
3298                         }
3299                     }
3300                     next_out = i;
3301                 } else if (started == STARTED_HOLDINGS) {
3302                     int gamenum;
3303                     char new_piece[MSG_SIZ];
3304                     started = STARTED_NONE;
3305                     parse[parse_pos] = NULLCHAR;
3306                     if (appData.debugMode)
3307                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3308                                                         parse, currentMove);
3309                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3310                         gamenum == ics_gamenum) {
3311                         if (gameInfo.variant == VariantNormal) {
3312                           /* [HGM] We seem to switch variant during a game!
3313                            * Presumably no holdings were displayed, so we have
3314                            * to move the position two files to the right to
3315                            * create room for them!
3316                            */
3317                           VariantClass newVariant;
3318                           switch(gameInfo.boardWidth) { // base guess on board width
3319                                 case 9:  newVariant = VariantShogi; break;
3320                                 case 10: newVariant = VariantGreat; break;
3321                                 default: newVariant = VariantCrazyhouse; break;
3322                           }
3323                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3324                           /* Get a move list just to see the header, which
3325                              will tell us whether this is really bug or zh */
3326                           if (ics_getting_history == H_FALSE) {
3327                             ics_getting_history = H_REQUESTED;
3328                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3329                             SendToICS(str);
3330                           }
3331                         }
3332                         new_piece[0] = NULLCHAR;
3333                         sscanf(parse, "game %d white [%s black [%s <- %s",
3334                                &gamenum, white_holding, black_holding,
3335                                new_piece);
3336                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3337                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3338                         /* [HGM] copy holdings to board holdings area */
3339                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3340                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3341                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3342 #if ZIPPY
3343                         if (appData.zippyPlay && first.initDone) {
3344                             ZippyHoldings(white_holding, black_holding,
3345                                           new_piece);
3346                         }
3347 #endif /*ZIPPY*/
3348                         if (tinyLayout || smallLayout) {
3349                             char wh[16], bh[16];
3350                             PackHolding(wh, white_holding);
3351                             PackHolding(bh, black_holding);
3352                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3353                                     gameInfo.white, gameInfo.black);
3354                         } else {
3355                             sprintf(str, "%s [%s] vs. %s [%s]",
3356                                     gameInfo.white, white_holding,
3357                                     gameInfo.black, black_holding);
3358                         }
3359
3360                         DrawPosition(FALSE, boards[currentMove]);
3361                         DisplayTitle(str);
3362                     }
3363                     /* Suppress following prompt */
3364                     if (looking_at(buf, &i, "*% ")) {
3365                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3366                         savingComment = FALSE;
3367                         suppressKibitz = 0;
3368                     }
3369                     next_out = i;
3370                 }
3371                 continue;
3372             }
3373
3374             i++;                /* skip unparsed character and loop back */
3375         }
3376         
3377         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3378 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3379 //          SendToPlayer(&buf[next_out], i - next_out);
3380             started != STARTED_HOLDINGS && leftover_start > next_out) {
3381             SendToPlayer(&buf[next_out], leftover_start - next_out);
3382             next_out = i;
3383         }
3384         
3385         leftover_len = buf_len - leftover_start;
3386         /* if buffer ends with something we couldn't parse,
3387            reparse it after appending the next read */
3388         
3389     } else if (count == 0) {
3390         RemoveInputSource(isr);
3391         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3392     } else {
3393         DisplayFatalError(_("Error reading from ICS"), error, 1);
3394     }
3395 }
3396
3397
3398 /* Board style 12 looks like this:
3399    
3400    <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
3401    
3402  * The "<12> " is stripped before it gets to this routine.  The two
3403  * trailing 0's (flip state and clock ticking) are later addition, and
3404  * some chess servers may not have them, or may have only the first.
3405  * Additional trailing fields may be added in the future.  
3406  */
3407
3408 #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"
3409
3410 #define RELATION_OBSERVING_PLAYED    0
3411 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3412 #define RELATION_PLAYING_MYMOVE      1
3413 #define RELATION_PLAYING_NOTMYMOVE  -1
3414 #define RELATION_EXAMINING           2
3415 #define RELATION_ISOLATED_BOARD     -3
3416 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3417
3418 void
3419 ParseBoard12(string)
3420      char *string;
3421
3422     GameMode newGameMode;
3423     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3424     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3425     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3426     char to_play, board_chars[200];
3427     char move_str[500], str[500], elapsed_time[500];
3428     char black[32], white[32];
3429     Board board;
3430     int prevMove = currentMove;
3431     int ticking = 2;
3432     ChessMove moveType;
3433     int fromX, fromY, toX, toY;
3434     char promoChar;
3435     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3436     char *bookHit = NULL; // [HGM] book
3437     Boolean weird = FALSE, reqFlag = FALSE;
3438
3439     fromX = fromY = toX = toY = -1;
3440     
3441     newGame = FALSE;
3442
3443     if (appData.debugMode)
3444       fprintf(debugFP, _("Parsing board: %s\n"), string);
3445
3446     move_str[0] = NULLCHAR;
3447     elapsed_time[0] = NULLCHAR;
3448     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3449         int  i = 0, j;
3450         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3451             if(string[i] == ' ') { ranks++; files = 0; }
3452             else files++;
3453             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3454             i++;
3455         }
3456         for(j = 0; j <i; j++) board_chars[j] = string[j];
3457         board_chars[i] = '\0';
3458         string += i + 1;
3459     }
3460     n = sscanf(string, PATTERN, &to_play, &double_push,
3461                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3462                &gamenum, white, black, &relation, &basetime, &increment,
3463                &white_stren, &black_stren, &white_time, &black_time,
3464                &moveNum, str, elapsed_time, move_str, &ics_flip,
3465                &ticking);
3466
3467     if (n < 21) {
3468         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3469         DisplayError(str, 0);
3470         return;
3471     }
3472
3473     /* Convert the move number to internal form */
3474     moveNum = (moveNum - 1) * 2;
3475     if (to_play == 'B') moveNum++;
3476     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3477       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3478                         0, 1);
3479       return;
3480     }
3481     
3482     switch (relation) {
3483       case RELATION_OBSERVING_PLAYED:
3484       case RELATION_OBSERVING_STATIC:
3485         if (gamenum == -1) {
3486             /* Old ICC buglet */
3487             relation = RELATION_OBSERVING_STATIC;
3488         }
3489         newGameMode = IcsObserving;
3490         break;
3491       case RELATION_PLAYING_MYMOVE:
3492       case RELATION_PLAYING_NOTMYMOVE:
3493         newGameMode =
3494           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3495             IcsPlayingWhite : IcsPlayingBlack;
3496         break;
3497       case RELATION_EXAMINING:
3498         newGameMode = IcsExamining;
3499         break;
3500       case RELATION_ISOLATED_BOARD:
3501       default:
3502         /* Just display this board.  If user was doing something else,
3503            we will forget about it until the next board comes. */ 
3504         newGameMode = IcsIdle;
3505         break;
3506       case RELATION_STARTING_POSITION:
3507         newGameMode = gameMode;
3508         break;
3509     }
3510     
3511     /* Modify behavior for initial board display on move listing
3512        of wild games.
3513        */
3514     switch (ics_getting_history) {
3515       case H_FALSE:
3516       case H_REQUESTED:
3517         break;
3518       case H_GOT_REQ_HEADER:
3519       case H_GOT_UNREQ_HEADER:
3520         /* This is the initial position of the current game */
3521         gamenum = ics_gamenum;
3522         moveNum = 0;            /* old ICS bug workaround */
3523         if (to_play == 'B') {
3524           startedFromSetupPosition = TRUE;
3525           blackPlaysFirst = TRUE;
3526           moveNum = 1;
3527           if (forwardMostMove == 0) forwardMostMove = 1;
3528           if (backwardMostMove == 0) backwardMostMove = 1;
3529           if (currentMove == 0) currentMove = 1;
3530         }
3531         newGameMode = gameMode;
3532         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3533         break;
3534       case H_GOT_UNWANTED_HEADER:
3535         /* This is an initial board that we don't want */
3536         return;
3537       case H_GETTING_MOVES:
3538         /* Should not happen */
3539         DisplayError(_("Error gathering move list: extra board"), 0);
3540         ics_getting_history = H_FALSE;
3541         return;
3542     }
3543
3544    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3545                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3546      /* [HGM] We seem to have switched variant unexpectedly
3547       * Try to guess new variant from board size
3548       */
3549           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3550           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3551           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3552           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3553           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3554           if(!weird) newVariant = VariantNormal;
3555           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3556           /* Get a move list just to see the header, which
3557              will tell us whether this is really bug or zh */
3558           if (ics_getting_history == H_FALSE) {
3559             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3560             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3561             SendToICS(str);
3562           }
3563     }
3564     
3565     /* Take action if this is the first board of a new game, or of a
3566        different game than is currently being displayed.  */
3567     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3568         relation == RELATION_ISOLATED_BOARD) {
3569         
3570         /* Forget the old game and get the history (if any) of the new one */
3571         if (gameMode != BeginningOfGame) {
3572           Reset(TRUE, TRUE);
3573         }
3574         newGame = TRUE;
3575         if (appData.autoRaiseBoard) BoardToTop();
3576         prevMove = -3;
3577         if (gamenum == -1) {
3578             newGameMode = IcsIdle;
3579         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3580                    appData.getMoveList && !reqFlag) {
3581             /* Need to get game history */
3582             ics_getting_history = H_REQUESTED;
3583             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3584             SendToICS(str);
3585         }
3586         
3587         /* Initially flip the board to have black on the bottom if playing
3588            black or if the ICS flip flag is set, but let the user change
3589            it with the Flip View button. */
3590         flipView = appData.autoFlipView ? 
3591           (newGameMode == IcsPlayingBlack) || ics_flip :
3592           appData.flipView;
3593         
3594         /* Done with values from previous mode; copy in new ones */
3595         gameMode = newGameMode;
3596         ModeHighlight();
3597         ics_gamenum = gamenum;
3598         if (gamenum == gs_gamenum) {
3599             int klen = strlen(gs_kind);
3600             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3601             sprintf(str, "ICS %s", gs_kind);
3602             gameInfo.event = StrSave(str);
3603         } else {
3604             gameInfo.event = StrSave("ICS game");
3605         }
3606         gameInfo.site = StrSave(appData.icsHost);
3607         gameInfo.date = PGNDate();
3608         gameInfo.round = StrSave("-");
3609         gameInfo.white = StrSave(white);
3610         gameInfo.black = StrSave(black);
3611         timeControl = basetime * 60 * 1000;
3612         timeControl_2 = 0;
3613         timeIncrement = increment * 1000;
3614         movesPerSession = 0;
3615         gameInfo.timeControl = TimeControlTagValue();
3616         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3617   if (appData.debugMode) {
3618     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3619     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3620     setbuf(debugFP, NULL);
3621   }
3622
3623         gameInfo.outOfBook = NULL;
3624         
3625         /* Do we have the ratings? */
3626         if (strcmp(player1Name, white) == 0 &&
3627             strcmp(player2Name, black) == 0) {
3628             if (appData.debugMode)
3629               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3630                       player1Rating, player2Rating);
3631             gameInfo.whiteRating = player1Rating;
3632             gameInfo.blackRating = player2Rating;
3633         } else if (strcmp(player2Name, white) == 0 &&
3634                    strcmp(player1Name, black) == 0) {
3635             if (appData.debugMode)
3636               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3637                       player2Rating, player1Rating);
3638             gameInfo.whiteRating = player2Rating;
3639             gameInfo.blackRating = player1Rating;
3640         }
3641         player1Name[0] = player2Name[0] = NULLCHAR;
3642
3643         /* Silence shouts if requested */
3644         if (appData.quietPlay &&
3645             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3646             SendToICS(ics_prefix);
3647             SendToICS("set shout 0\n");
3648         }
3649     }
3650     
3651     /* Deal with midgame name changes */
3652     if (!newGame) {
3653         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3654             if (gameInfo.white) free(gameInfo.white);
3655             gameInfo.white = StrSave(white);
3656         }
3657         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3658             if (gameInfo.black) free(gameInfo.black);
3659             gameInfo.black = StrSave(black);
3660         }
3661     }
3662     
3663     /* Throw away game result if anything actually changes in examine mode */
3664     if (gameMode == IcsExamining && !newGame) {
3665         gameInfo.result = GameUnfinished;
3666         if (gameInfo.resultDetails != NULL) {
3667             free(gameInfo.resultDetails);
3668             gameInfo.resultDetails = NULL;
3669         }
3670     }
3671     
3672     /* In pausing && IcsExamining mode, we ignore boards coming
3673        in if they are in a different variation than we are. */
3674     if (pauseExamInvalid) return;
3675     if (pausing && gameMode == IcsExamining) {
3676         if (moveNum <= pauseExamForwardMostMove) {
3677             pauseExamInvalid = TRUE;
3678             forwardMostMove = pauseExamForwardMostMove;
3679             return;
3680         }
3681     }
3682     
3683   if (appData.debugMode) {
3684     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3685   }
3686     /* Parse the board */
3687     for (k = 0; k < ranks; k++) {
3688       for (j = 0; j < files; j++)
3689         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3690       if(gameInfo.holdingsWidth > 1) {
3691            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3692            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3693       }
3694     }
3695     CopyBoard(boards[moveNum], board);
3696     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3697     if (moveNum == 0) {
3698         startedFromSetupPosition =
3699           !CompareBoards(board, initialPosition);
3700         if(startedFromSetupPosition)
3701             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3702     }
3703
3704     /* [HGM] Set castling rights. Take the outermost Rooks,
3705        to make it also work for FRC opening positions. Note that board12
3706        is really defective for later FRC positions, as it has no way to
3707        indicate which Rook can castle if they are on the same side of King.
3708        For the initial position we grant rights to the outermost Rooks,
3709        and remember thos rights, and we then copy them on positions
3710        later in an FRC game. This means WB might not recognize castlings with
3711        Rooks that have moved back to their original position as illegal,
3712        but in ICS mode that is not its job anyway.
3713     */
3714     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3715     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3716
3717         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3718             if(board[0][i] == WhiteRook) j = i;
3719         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3720         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3721             if(board[0][i] == WhiteRook) j = i;
3722         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3723         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3724             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3725         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3726         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3727             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3728         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3729
3730         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3731         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3732             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3733         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3734             if(board[BOARD_HEIGHT-1][k] == bKing)
3735                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3736         if(gameInfo.variant == VariantTwoKings) {
3737             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3738             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3739             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3740         }
3741     } else { int r;
3742         r = boards[moveNum][CASTLING][0] = initialRights[0];
3743         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3744         r = boards[moveNum][CASTLING][1] = initialRights[1];
3745         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3746         r = boards[moveNum][CASTLING][3] = initialRights[3];
3747         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3748         r = boards[moveNum][CASTLING][4] = initialRights[4];
3749         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3750         /* wildcastle kludge: always assume King has rights */
3751         r = boards[moveNum][CASTLING][2] = initialRights[2];
3752         r = boards[moveNum][CASTLING][5] = initialRights[5];
3753     }
3754     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3755     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3756
3757     
3758     if (ics_getting_history == H_GOT_REQ_HEADER ||
3759         ics_getting_history == H_GOT_UNREQ_HEADER) {
3760         /* This was an initial position from a move list, not
3761            the current position */
3762         return;
3763     }
3764     
3765     /* Update currentMove and known move number limits */
3766     newMove = newGame || moveNum > forwardMostMove;
3767
3768     if (newGame) {
3769         forwardMostMove = backwardMostMove = currentMove = moveNum;
3770         if (gameMode == IcsExamining && moveNum == 0) {
3771           /* Workaround for ICS limitation: we are not told the wild
3772              type when starting to examine a game.  But if we ask for
3773              the move list, the move list header will tell us */
3774             ics_getting_history = H_REQUESTED;
3775             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3776             SendToICS(str);
3777         }
3778     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3779                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3780 #if ZIPPY
3781         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3782         /* [HGM] applied this also to an engine that is silently watching        */
3783         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3784             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3785             gameInfo.variant == currentlyInitializedVariant) {
3786           takeback = forwardMostMove - moveNum;
3787           for (i = 0; i < takeback; i++) {
3788             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3789             SendToProgram("undo\n", &first);
3790           }
3791         }
3792 #endif
3793
3794         forwardMostMove = moveNum;
3795         if (!pausing || currentMove > forwardMostMove)
3796           currentMove = forwardMostMove;
3797     } else {
3798         /* New part of history that is not contiguous with old part */ 
3799         if (pausing && gameMode == IcsExamining) {
3800             pauseExamInvalid = TRUE;
3801             forwardMostMove = pauseExamForwardMostMove;
3802             return;
3803         }
3804         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3805 #if ZIPPY
3806             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3807                 // [HGM] when we will receive the move list we now request, it will be
3808                 // fed to the engine from the first move on. So if the engine is not
3809                 // in the initial position now, bring it there.
3810                 InitChessProgram(&first, 0);
3811             }
3812 #endif
3813             ics_getting_history = H_REQUESTED;
3814             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3815             SendToICS(str);
3816         }
3817         forwardMostMove = backwardMostMove = currentMove = moveNum;
3818     }
3819     
3820     /* Update the clocks */
3821     if (strchr(elapsed_time, '.')) {
3822       /* Time is in ms */
3823       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3824       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3825     } else {
3826       /* Time is in seconds */
3827       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3828       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3829     }
3830       
3831
3832 #if ZIPPY
3833     if (appData.zippyPlay && newGame &&
3834         gameMode != IcsObserving && gameMode != IcsIdle &&
3835         gameMode != IcsExamining)
3836       ZippyFirstBoard(moveNum, basetime, increment);
3837 #endif
3838     
3839     /* Put the move on the move list, first converting
3840        to canonical algebraic form. */
3841     if (moveNum > 0) {
3842   if (appData.debugMode) {
3843     if (appData.debugMode) { int f = forwardMostMove;
3844         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3845                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3846                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3847     }
3848     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3849     fprintf(debugFP, "moveNum = %d\n", moveNum);
3850     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3851     setbuf(debugFP, NULL);
3852   }
3853         if (moveNum <= backwardMostMove) {
3854             /* We don't know what the board looked like before
3855                this move.  Punt. */
3856             strcpy(parseList[moveNum - 1], move_str);
3857             strcat(parseList[moveNum - 1], " ");
3858             strcat(parseList[moveNum - 1], elapsed_time);
3859             moveList[moveNum - 1][0] = NULLCHAR;
3860         } else if (strcmp(move_str, "none") == 0) {
3861             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3862             /* Again, we don't know what the board looked like;
3863                this is really the start of the game. */
3864             parseList[moveNum - 1][0] = NULLCHAR;
3865             moveList[moveNum - 1][0] = NULLCHAR;
3866             backwardMostMove = moveNum;
3867             startedFromSetupPosition = TRUE;
3868             fromX = fromY = toX = toY = -1;
3869         } else {
3870           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3871           //                 So we parse the long-algebraic move string in stead of the SAN move
3872           int valid; char buf[MSG_SIZ], *prom;
3873
3874           // str looks something like "Q/a1-a2"; kill the slash
3875           if(str[1] == '/') 
3876                 sprintf(buf, "%c%s", str[0], str+2);
3877           else  strcpy(buf, str); // might be castling
3878           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3879                 strcat(buf, prom); // long move lacks promo specification!
3880           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3881                 if(appData.debugMode) 
3882                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3883                 strcpy(move_str, buf);
3884           }
3885           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3886                                 &fromX, &fromY, &toX, &toY, &promoChar)
3887                || ParseOneMove(buf, moveNum - 1, &moveType,
3888                                 &fromX, &fromY, &toX, &toY, &promoChar);
3889           // end of long SAN patch
3890           if (valid) {
3891             (void) CoordsToAlgebraic(boards[moveNum - 1],
3892                                      PosFlags(moveNum - 1),
3893                                      fromY, fromX, toY, toX, promoChar,
3894                                      parseList[moveNum-1]);
3895             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3896               case MT_NONE:
3897               case MT_STALEMATE:
3898               default:
3899                 break;
3900               case MT_CHECK:
3901                 if(gameInfo.variant != VariantShogi)
3902                     strcat(parseList[moveNum - 1], "+");
3903                 break;
3904               case MT_CHECKMATE:
3905               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3906                 strcat(parseList[moveNum - 1], "#");
3907                 break;
3908             }
3909             strcat(parseList[moveNum - 1], " ");
3910             strcat(parseList[moveNum - 1], elapsed_time);
3911             /* currentMoveString is set as a side-effect of ParseOneMove */
3912             strcpy(moveList[moveNum - 1], currentMoveString);
3913             strcat(moveList[moveNum - 1], "\n");
3914           } else {
3915             /* Move from ICS was illegal!?  Punt. */
3916   if (appData.debugMode) {
3917     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3918     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3919   }
3920             strcpy(parseList[moveNum - 1], move_str);
3921             strcat(parseList[moveNum - 1], " ");
3922             strcat(parseList[moveNum - 1], elapsed_time);
3923             moveList[moveNum - 1][0] = NULLCHAR;
3924             fromX = fromY = toX = toY = -1;
3925           }
3926         }
3927   if (appData.debugMode) {
3928     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3929     setbuf(debugFP, NULL);
3930   }
3931
3932 #if ZIPPY
3933         /* Send move to chess program (BEFORE animating it). */
3934         if (appData.zippyPlay && !newGame && newMove && 
3935            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3936
3937             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3938                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3939                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3940                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3941                             move_str);
3942                     DisplayError(str, 0);
3943                 } else {
3944                     if (first.sendTime) {
3945                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3946                     }
3947                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3948                     if (firstMove && !bookHit) {
3949                         firstMove = FALSE;
3950                         if (first.useColors) {
3951                           SendToProgram(gameMode == IcsPlayingWhite ?
3952                                         "white\ngo\n" :
3953                                         "black\ngo\n", &first);
3954                         } else {
3955                           SendToProgram("go\n", &first);
3956                         }
3957                         first.maybeThinking = TRUE;
3958                     }
3959                 }
3960             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3961               if (moveList[moveNum - 1][0] == NULLCHAR) {
3962                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3963                 DisplayError(str, 0);
3964               } else {
3965                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3966                 SendMoveToProgram(moveNum - 1, &first);
3967               }
3968             }
3969         }
3970 #endif
3971     }
3972
3973     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3974         /* If move comes from a remote source, animate it.  If it
3975            isn't remote, it will have already been animated. */
3976         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3977             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3978         }
3979         if (!pausing && appData.highlightLastMove) {
3980             SetHighlights(fromX, fromY, toX, toY);
3981         }
3982     }
3983     
3984     /* Start the clocks */
3985     whiteFlag = blackFlag = FALSE;
3986     appData.clockMode = !(basetime == 0 && increment == 0);
3987     if (ticking == 0) {
3988       ics_clock_paused = TRUE;
3989       StopClocks();
3990     } else if (ticking == 1) {
3991       ics_clock_paused = FALSE;
3992     }
3993     if (gameMode == IcsIdle ||
3994         relation == RELATION_OBSERVING_STATIC ||
3995         relation == RELATION_EXAMINING ||
3996         ics_clock_paused)
3997       DisplayBothClocks();
3998     else
3999       StartClocks();
4000     
4001     /* Display opponents and material strengths */
4002     if (gameInfo.variant != VariantBughouse &&
4003         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4004         if (tinyLayout || smallLayout) {
4005             if(gameInfo.variant == VariantNormal)
4006                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4007                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4008                     basetime, increment);
4009             else
4010                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4011                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4012                     basetime, increment, (int) gameInfo.variant);
4013         } else {
4014             if(gameInfo.variant == VariantNormal)
4015                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4016                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4017                     basetime, increment);
4018             else
4019                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4020                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4021                     basetime, increment, VariantName(gameInfo.variant));
4022         }
4023         DisplayTitle(str);
4024   if (appData.debugMode) {
4025     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4026   }
4027     }
4028
4029    
4030     /* Display the board */
4031     if (!pausing && !appData.noGUI) {
4032       
4033       if (appData.premove)
4034           if (!gotPremove || 
4035              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4036              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4037               ClearPremoveHighlights();
4038
4039       DrawPosition(FALSE, boards[currentMove]);
4040       DisplayMove(moveNum - 1);
4041       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4042             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4043               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4044         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4045       }
4046     }
4047
4048     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4049 #if ZIPPY
4050     if(bookHit) { // [HGM] book: simulate book reply
4051         static char bookMove[MSG_SIZ]; // a bit generous?
4052
4053         programStats.nodes = programStats.depth = programStats.time = 
4054         programStats.score = programStats.got_only_move = 0;
4055         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4056
4057         strcpy(bookMove, "move ");
4058         strcat(bookMove, bookHit);
4059         HandleMachineMove(bookMove, &first);
4060     }
4061 #endif
4062 }
4063
4064 void
4065 GetMoveListEvent()
4066 {
4067     char buf[MSG_SIZ];
4068     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4069         ics_getting_history = H_REQUESTED;
4070         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4071         SendToICS(buf);
4072     }
4073 }
4074
4075 void
4076 AnalysisPeriodicEvent(force)
4077      int force;
4078 {
4079     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4080          && !force) || !appData.periodicUpdates)
4081       return;
4082
4083     /* Send . command to Crafty to collect stats */
4084     SendToProgram(".\n", &first);
4085
4086     /* Don't send another until we get a response (this makes
4087        us stop sending to old Crafty's which don't understand
4088        the "." command (sending illegal cmds resets node count & time,
4089        which looks bad)) */
4090     programStats.ok_to_send = 0;
4091 }
4092
4093 void ics_update_width(new_width)
4094         int new_width;
4095 {
4096         ics_printf("set width %d\n", new_width);
4097 }
4098
4099 void
4100 SendMoveToProgram(moveNum, cps)
4101      int moveNum;
4102      ChessProgramState *cps;
4103 {
4104     char buf[MSG_SIZ];
4105
4106     if (cps->useUsermove) {
4107       SendToProgram("usermove ", cps);
4108     }
4109     if (cps->useSAN) {
4110       char *space;
4111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4112         int len = space - parseList[moveNum];
4113         memcpy(buf, parseList[moveNum], len);
4114         buf[len++] = '\n';
4115         buf[len] = NULLCHAR;
4116       } else {
4117         sprintf(buf, "%s\n", parseList[moveNum]);
4118       }
4119       SendToProgram(buf, cps);
4120     } else {
4121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4122         AlphaRank(moveList[moveNum], 4);
4123         SendToProgram(moveList[moveNum], cps);
4124         AlphaRank(moveList[moveNum], 4); // and back
4125       } else
4126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4127        * the engine. It would be nice to have a better way to identify castle 
4128        * moves here. */
4129       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4130                                                                          && cps->useOOCastle) {
4131         int fromX = moveList[moveNum][0] - AAA; 
4132         int fromY = moveList[moveNum][1] - ONE;
4133         int toX = moveList[moveNum][2] - AAA; 
4134         int toY = moveList[moveNum][3] - ONE;
4135         if((boards[moveNum][fromY][fromX] == WhiteKing 
4136             && boards[moveNum][toY][toX] == WhiteRook)
4137            || (boards[moveNum][fromY][fromX] == BlackKing 
4138                && boards[moveNum][toY][toX] == BlackRook)) {
4139           if(toX > fromX) SendToProgram("O-O\n", cps);
4140           else SendToProgram("O-O-O\n", cps);
4141         }
4142         else SendToProgram(moveList[moveNum], cps);
4143       }
4144       else SendToProgram(moveList[moveNum], cps);
4145       /* End of additions by Tord */
4146     }
4147
4148     /* [HGM] setting up the opening has brought engine in force mode! */
4149     /*       Send 'go' if we are in a mode where machine should play. */
4150     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4151         (gameMode == TwoMachinesPlay   ||
4152 #ifdef ZIPPY
4153          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4154 #endif
4155          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4156         SendToProgram("go\n", cps);
4157   if (appData.debugMode) {
4158     fprintf(debugFP, "(extra)\n");
4159   }
4160     }
4161     setboardSpoiledMachineBlack = 0;
4162 }
4163
4164 void
4165 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4166      ChessMove moveType;
4167      int fromX, fromY, toX, toY;
4168 {
4169     char user_move[MSG_SIZ];
4170
4171     switch (moveType) {
4172       default:
4173         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4174                 (int)moveType, fromX, fromY, toX, toY);
4175         DisplayError(user_move + strlen("say "), 0);
4176         break;
4177       case WhiteKingSideCastle:
4178       case BlackKingSideCastle:
4179       case WhiteQueenSideCastleWild:
4180       case BlackQueenSideCastleWild:
4181       /* PUSH Fabien */
4182       case WhiteHSideCastleFR:
4183       case BlackHSideCastleFR:
4184       /* POP Fabien */
4185         sprintf(user_move, "o-o\n");
4186         break;
4187       case WhiteQueenSideCastle:
4188       case BlackQueenSideCastle:
4189       case WhiteKingSideCastleWild:
4190       case BlackKingSideCastleWild:
4191       /* PUSH Fabien */
4192       case WhiteASideCastleFR:
4193       case BlackASideCastleFR:
4194       /* POP Fabien */
4195         sprintf(user_move, "o-o-o\n");
4196         break;
4197       case WhitePromotionQueen:
4198       case BlackPromotionQueen:
4199       case WhitePromotionRook:
4200       case BlackPromotionRook:
4201       case WhitePromotionBishop:
4202       case BlackPromotionBishop:
4203       case WhitePromotionKnight:
4204       case BlackPromotionKnight:
4205       case WhitePromotionKing:
4206       case BlackPromotionKing:
4207       case WhitePromotionChancellor:
4208       case BlackPromotionChancellor:
4209       case WhitePromotionArchbishop:
4210       case BlackPromotionArchbishop:
4211         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4212             sprintf(user_move, "%c%c%c%c=%c\n",
4213                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4214                 PieceToChar(WhiteFerz));
4215         else if(gameInfo.variant == VariantGreat)
4216             sprintf(user_move, "%c%c%c%c=%c\n",
4217                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4218                 PieceToChar(WhiteMan));
4219         else
4220             sprintf(user_move, "%c%c%c%c=%c\n",
4221                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4222                 PieceToChar(PromoPiece(moveType)));
4223         break;
4224       case WhiteDrop:
4225       case BlackDrop:
4226         sprintf(user_move, "%c@%c%c\n",
4227                 ToUpper(PieceToChar((ChessSquare) fromX)),
4228                 AAA + toX, ONE + toY);
4229         break;
4230       case NormalMove:
4231       case WhiteCapturesEnPassant:
4232       case BlackCapturesEnPassant:
4233       case IllegalMove:  /* could be a variant we don't quite understand */
4234         sprintf(user_move, "%c%c%c%c\n",
4235                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4236         break;
4237     }
4238     SendToICS(user_move);
4239     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4240         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4241 }
4242
4243 void
4244 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4245      int rf, ff, rt, ft;
4246      char promoChar;
4247      char move[7];
4248 {
4249     if (rf == DROP_RANK) {
4250         sprintf(move, "%c@%c%c\n",
4251                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4252     } else {
4253         if (promoChar == 'x' || promoChar == NULLCHAR) {
4254             sprintf(move, "%c%c%c%c\n",
4255                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4256         } else {
4257             sprintf(move, "%c%c%c%c%c\n",
4258                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4259         }
4260     }
4261 }
4262
4263 void
4264 ProcessICSInitScript(f)
4265      FILE *f;
4266 {
4267     char buf[MSG_SIZ];
4268
4269     while (fgets(buf, MSG_SIZ, f)) {
4270         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4271     }
4272
4273     fclose(f);
4274 }
4275
4276
4277 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4278 void
4279 AlphaRank(char *move, int n)
4280 {
4281 //    char *p = move, c; int x, y;
4282
4283     if (appData.debugMode) {
4284         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4285     }
4286
4287     if(move[1]=='*' && 
4288        move[2]>='0' && move[2]<='9' &&
4289        move[3]>='a' && move[3]<='x'    ) {
4290         move[1] = '@';
4291         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4292         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4293     } else
4294     if(move[0]>='0' && move[0]<='9' &&
4295        move[1]>='a' && move[1]<='x' &&
4296        move[2]>='0' && move[2]<='9' &&
4297        move[3]>='a' && move[3]<='x'    ) {
4298         /* input move, Shogi -> normal */
4299         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4300         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4301         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4302         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4303     } else
4304     if(move[1]=='@' &&
4305        move[3]>='0' && move[3]<='9' &&
4306        move[2]>='a' && move[2]<='x'    ) {
4307         move[1] = '*';
4308         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4309         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4310     } else
4311     if(
4312        move[0]>='a' && move[0]<='x' &&
4313        move[3]>='0' && move[3]<='9' &&
4314        move[2]>='a' && move[2]<='x'    ) {
4315          /* output move, normal -> Shogi */
4316         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4317         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4318         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4319         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4320         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4321     }
4322     if (appData.debugMode) {
4323         fprintf(debugFP, "   out = '%s'\n", move);
4324     }
4325 }
4326
4327 /* Parser for moves from gnuchess, ICS, or user typein box */
4328 Boolean
4329 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4330      char *move;
4331      int moveNum;
4332      ChessMove *moveType;
4333      int *fromX, *fromY, *toX, *toY;
4334      char *promoChar;
4335 {       
4336     if (appData.debugMode) {
4337         fprintf(debugFP, "move to parse: %s\n", move);
4338     }
4339     *moveType = yylexstr(moveNum, move);
4340
4341     switch (*moveType) {
4342       case WhitePromotionChancellor:
4343       case BlackPromotionChancellor:
4344       case WhitePromotionArchbishop:
4345       case BlackPromotionArchbishop:
4346       case WhitePromotionQueen:
4347       case BlackPromotionQueen:
4348       case WhitePromotionRook:
4349       case BlackPromotionRook:
4350       case WhitePromotionBishop:
4351       case BlackPromotionBishop:
4352       case WhitePromotionKnight:
4353       case BlackPromotionKnight:
4354       case WhitePromotionKing:
4355       case BlackPromotionKing:
4356       case NormalMove:
4357       case WhiteCapturesEnPassant:
4358       case BlackCapturesEnPassant:
4359       case WhiteKingSideCastle:
4360       case WhiteQueenSideCastle:
4361       case BlackKingSideCastle:
4362       case BlackQueenSideCastle:
4363       case WhiteKingSideCastleWild:
4364       case WhiteQueenSideCastleWild:
4365       case BlackKingSideCastleWild:
4366       case BlackQueenSideCastleWild:
4367       /* Code added by Tord: */
4368       case WhiteHSideCastleFR:
4369       case WhiteASideCastleFR:
4370       case BlackHSideCastleFR:
4371       case BlackASideCastleFR:
4372       /* End of code added by Tord */
4373       case IllegalMove:         /* bug or odd chess variant */
4374         *fromX = currentMoveString[0] - AAA;
4375         *fromY = currentMoveString[1] - ONE;
4376         *toX = currentMoveString[2] - AAA;
4377         *toY = currentMoveString[3] - ONE;
4378         *promoChar = currentMoveString[4];
4379         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4380             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4381     if (appData.debugMode) {
4382         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4383     }
4384             *fromX = *fromY = *toX = *toY = 0;
4385             return FALSE;
4386         }
4387         if (appData.testLegality) {
4388           return (*moveType != IllegalMove);
4389         } else {
4390           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4391                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4392         }
4393
4394       case WhiteDrop:
4395       case BlackDrop:
4396         *fromX = *moveType == WhiteDrop ?
4397           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4398           (int) CharToPiece(ToLower(currentMoveString[0]));
4399         *fromY = DROP_RANK;
4400         *toX = currentMoveString[2] - AAA;
4401         *toY = currentMoveString[3] - ONE;
4402         *promoChar = NULLCHAR;
4403         return TRUE;
4404
4405       case AmbiguousMove:
4406       case ImpossibleMove:
4407       case (ChessMove) 0:       /* end of file */
4408       case ElapsedTime:
4409       case Comment:
4410       case PGNTag:
4411       case NAG:
4412       case WhiteWins:
4413       case BlackWins:
4414       case GameIsDrawn:
4415       default:
4416     if (appData.debugMode) {
4417         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4418     }
4419         /* bug? */
4420         *fromX = *fromY = *toX = *toY = 0;
4421         *promoChar = NULLCHAR;
4422         return FALSE;
4423     }
4424 }
4425
4426
4427 void
4428 ParsePV(char *pv)
4429 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4430   int fromX, fromY, toX, toY; char promoChar;
4431   ChessMove moveType;
4432   Boolean valid;
4433   int nr = 0;
4434
4435   endPV = forwardMostMove;
4436   do {
4437     while(*pv == ' ') pv++;
4438     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4439     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4440 if(appData.debugMode){
4441 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4442 }
4443     if(!valid && nr == 0 &&
4444        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4445         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4446     }
4447     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4448     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4449     nr++;
4450     if(endPV+1 > framePtr) break; // no space, truncate
4451     if(!valid) break;
4452     endPV++;
4453     CopyBoard(boards[endPV], boards[endPV-1]);
4454     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4455     moveList[endPV-1][0] = fromX + AAA;
4456     moveList[endPV-1][1] = fromY + ONE;
4457     moveList[endPV-1][2] = toX + AAA;
4458     moveList[endPV-1][3] = toY + ONE;
4459     parseList[endPV-1][0] = NULLCHAR;
4460   } while(valid);
4461   currentMove = endPV;
4462   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4463   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4464                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4465   DrawPosition(TRUE, boards[currentMove]);
4466 }
4467
4468 static int lastX, lastY;
4469
4470 Boolean
4471 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4472 {
4473         int startPV;
4474
4475         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4476         lastX = x; lastY = y;
4477         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4478         startPV = index;
4479       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4480       index = startPV;
4481         while(buf[index] && buf[index] != '\n') index++;
4482         buf[index] = 0;
4483         ParsePV(buf+startPV);
4484         *start = startPV; *end = index-1;
4485         return TRUE;
4486 }
4487
4488 Boolean
4489 LoadPV(int x, int y)
4490 { // called on right mouse click to load PV
4491   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4492   lastX = x; lastY = y;
4493   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4494   return TRUE;
4495 }
4496
4497 void
4498 UnLoadPV()
4499 {
4500   if(endPV < 0) return;
4501   endPV = -1;
4502   currentMove = forwardMostMove;
4503   ClearPremoveHighlights();
4504   DrawPosition(TRUE, boards[currentMove]);
4505 }
4506
4507 void
4508 MovePV(int x, int y, int h)
4509 { // step through PV based on mouse coordinates (called on mouse move)
4510   int margin = h>>3, step = 0;
4511
4512   if(endPV < 0) return;
4513   // we must somehow check if right button is still down (might be released off board!)
4514   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4515   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4516   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4517   if(!step) return;
4518   lastX = x; lastY = y;
4519   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4520   currentMove += step;
4521   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4522   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4523                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4524   DrawPosition(FALSE, boards[currentMove]);
4525 }
4526
4527
4528 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4529 // All positions will have equal probability, but the current method will not provide a unique
4530 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4531 #define DARK 1
4532 #define LITE 2
4533 #define ANY 3
4534
4535 int squaresLeft[4];
4536 int piecesLeft[(int)BlackPawn];
4537 int seed, nrOfShuffles;
4538
4539 void GetPositionNumber()
4540 {       // sets global variable seed
4541         int i;
4542
4543         seed = appData.defaultFrcPosition;
4544         if(seed < 0) { // randomize based on time for negative FRC position numbers
4545                 for(i=0; i<50; i++) seed += random();
4546                 seed = random() ^ random() >> 8 ^ random() << 8;
4547                 if(seed<0) seed = -seed;
4548         }
4549 }
4550
4551 int put(Board board, int pieceType, int rank, int n, int shade)
4552 // put the piece on the (n-1)-th empty squares of the given shade
4553 {
4554         int i;
4555
4556         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4557                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4558                         board[rank][i] = (ChessSquare) pieceType;
4559                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4560                         squaresLeft[ANY]--;
4561                         piecesLeft[pieceType]--; 
4562                         return i;
4563                 }
4564         }
4565         return -1;
4566 }
4567
4568
4569 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4570 // calculate where the next piece goes, (any empty square), and put it there
4571 {
4572         int i;
4573
4574         i = seed % squaresLeft[shade];
4575         nrOfShuffles *= squaresLeft[shade];
4576         seed /= squaresLeft[shade];
4577         put(board, pieceType, rank, i, shade);
4578 }
4579
4580 void AddTwoPieces(Board board, int pieceType, int rank)
4581 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4582 {
4583         int i, n=squaresLeft[ANY], j=n-1, k;
4584
4585         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4586         i = seed % k;  // pick one
4587         nrOfShuffles *= k;
4588         seed /= k;
4589         while(i >= j) i -= j--;
4590         j = n - 1 - j; i += j;
4591         put(board, pieceType, rank, j, ANY);
4592         put(board, pieceType, rank, i, ANY);
4593 }
4594
4595 void SetUpShuffle(Board board, int number)
4596 {
4597         int i, p, first=1;
4598
4599         GetPositionNumber(); nrOfShuffles = 1;
4600
4601         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4602         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4603         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4604
4605         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4606
4607         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4608             p = (int) board[0][i];
4609             if(p < (int) BlackPawn) piecesLeft[p] ++;
4610             board[0][i] = EmptySquare;
4611         }
4612
4613         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4614             // shuffles restricted to allow normal castling put KRR first
4615             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4616                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4617             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4618                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4619             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4620                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4621             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4622                 put(board, WhiteRook, 0, 0, ANY);
4623             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4624         }
4625
4626         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4627             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4628             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4629                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4630                 while(piecesLeft[p] >= 2) {
4631                     AddOnePiece(board, p, 0, LITE);
4632                     AddOnePiece(board, p, 0, DARK);
4633                 }
4634                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4635             }
4636
4637         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4638             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4639             // but we leave King and Rooks for last, to possibly obey FRC restriction
4640             if(p == (int)WhiteRook) continue;
4641             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4642             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4643         }
4644
4645         // now everything is placed, except perhaps King (Unicorn) and Rooks
4646
4647         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4648             // Last King gets castling rights
4649             while(piecesLeft[(int)WhiteUnicorn]) {
4650                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4651                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4652             }
4653
4654             while(piecesLeft[(int)WhiteKing]) {
4655                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4656                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4657             }
4658
4659
4660         } else {
4661             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4662             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4663         }
4664
4665         // Only Rooks can be left; simply place them all
4666         while(piecesLeft[(int)WhiteRook]) {
4667                 i = put(board, WhiteRook, 0, 0, ANY);
4668                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4669                         if(first) {
4670                                 first=0;
4671                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4672                         }
4673                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4674                 }
4675         }
4676         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4677             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4678         }
4679
4680         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4681 }
4682
4683 int SetCharTable( char *table, const char * map )
4684 /* [HGM] moved here from winboard.c because of its general usefulness */
4685 /*       Basically a safe strcpy that uses the last character as King */
4686 {
4687     int result = FALSE; int NrPieces;
4688
4689     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4690                     && NrPieces >= 12 && !(NrPieces&1)) {
4691         int i; /* [HGM] Accept even length from 12 to 34 */
4692
4693         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4694         for( i=0; i<NrPieces/2-1; i++ ) {
4695             table[i] = map[i];
4696             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4697         }
4698         table[(int) WhiteKing]  = map[NrPieces/2-1];
4699         table[(int) BlackKing]  = map[NrPieces-1];
4700
4701         result = TRUE;
4702     }
4703
4704     return result;
4705 }
4706
4707 void Prelude(Board board)
4708 {       // [HGM] superchess: random selection of exo-pieces
4709         int i, j, k; ChessSquare p; 
4710         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4711
4712         GetPositionNumber(); // use FRC position number
4713
4714         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4715             SetCharTable(pieceToChar, appData.pieceToCharTable);
4716             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4717                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4718         }
4719
4720         j = seed%4;                 seed /= 4; 
4721         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4722         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4723         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4724         j = seed%3 + (seed%3 >= j); seed /= 3; 
4725         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4726         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4727         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4728         j = seed%3;                 seed /= 3; 
4729         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4730         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4731         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4732         j = seed%2 + (seed%2 >= j); seed /= 2; 
4733         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4734         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4735         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4736         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4737         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4738         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4739         put(board, exoPieces[0],    0, 0, ANY);
4740         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4741 }
4742
4743 void
4744 InitPosition(redraw)
4745      int redraw;
4746 {
4747     ChessSquare (* pieces)[BOARD_FILES];
4748     int i, j, pawnRow, overrule,
4749     oldx = gameInfo.boardWidth,
4750     oldy = gameInfo.boardHeight,
4751     oldh = gameInfo.holdingsWidth,
4752     oldv = gameInfo.variant;
4753
4754     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4755
4756     /* [AS] Initialize pv info list [HGM] and game status */
4757     {
4758         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4759             pvInfoList[i].depth = 0;
4760             boards[i][EP_STATUS] = EP_NONE;
4761             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4762         }
4763
4764         initialRulePlies = 0; /* 50-move counter start */
4765
4766         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4767         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4768     }
4769
4770     
4771     /* [HGM] logic here is completely changed. In stead of full positions */
4772     /* the initialized data only consist of the two backranks. The switch */
4773     /* selects which one we will use, which is than copied to the Board   */
4774     /* initialPosition, which for the rest is initialized by Pawns and    */
4775     /* empty squares. This initial position is then copied to boards[0],  */
4776     /* possibly after shuffling, so that it remains available.            */
4777
4778     gameInfo.holdingsWidth = 0; /* default board sizes */
4779     gameInfo.boardWidth    = 8;
4780     gameInfo.boardHeight   = 8;
4781     gameInfo.holdingsSize  = 0;
4782     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4783     for(i=0; i<BOARD_FILES-2; i++)
4784       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4785     initialPosition[EP_STATUS] = EP_NONE;
4786     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4787
4788     switch (gameInfo.variant) {
4789     case VariantFischeRandom:
4790       shuffleOpenings = TRUE;
4791     default:
4792       pieces = FIDEArray;
4793       break;
4794     case VariantShatranj:
4795       pieces = ShatranjArray;
4796       nrCastlingRights = 0;
4797       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4798       break;
4799     case VariantMakruk:
4800       pieces = makrukArray;
4801       nrCastlingRights = 0;
4802       startedFromSetupPosition = TRUE;
4803       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4804       break;
4805     case VariantTwoKings:
4806       pieces = twoKingsArray;
4807       break;
4808     case VariantCapaRandom:
4809       shuffleOpenings = TRUE;
4810     case VariantCapablanca:
4811       pieces = CapablancaArray;
4812       gameInfo.boardWidth = 10;
4813       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4814       break;
4815     case VariantGothic:
4816       pieces = GothicArray;
4817       gameInfo.boardWidth = 10;
4818       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4819       break;
4820     case VariantJanus:
4821       pieces = JanusArray;
4822       gameInfo.boardWidth = 10;
4823       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4824       nrCastlingRights = 6;
4825         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4826         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4827         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4828         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4829         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4830         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4831       break;
4832     case VariantFalcon:
4833       pieces = FalconArray;
4834       gameInfo.boardWidth = 10;
4835       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4836       break;
4837     case VariantXiangqi:
4838       pieces = XiangqiArray;
4839       gameInfo.boardWidth  = 9;
4840       gameInfo.boardHeight = 10;
4841       nrCastlingRights = 0;
4842       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4843       break;
4844     case VariantShogi:
4845       pieces = ShogiArray;
4846       gameInfo.boardWidth  = 9;
4847       gameInfo.boardHeight = 9;
4848       gameInfo.holdingsSize = 7;
4849       nrCastlingRights = 0;
4850       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4851       break;
4852     case VariantCourier:
4853       pieces = CourierArray;
4854       gameInfo.boardWidth  = 12;
4855       nrCastlingRights = 0;
4856       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4857       break;
4858     case VariantKnightmate:
4859       pieces = KnightmateArray;
4860       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4861       break;
4862     case VariantFairy:
4863       pieces = fairyArray;
4864       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4865       break;
4866     case VariantGreat:
4867       pieces = GreatArray;
4868       gameInfo.boardWidth = 10;
4869       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4870       gameInfo.holdingsSize = 8;
4871       break;
4872     case VariantSuper:
4873       pieces = FIDEArray;
4874       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4875       gameInfo.holdingsSize = 8;
4876       startedFromSetupPosition = TRUE;
4877       break;
4878     case VariantCrazyhouse:
4879     case VariantBughouse:
4880       pieces = FIDEArray;
4881       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4882       gameInfo.holdingsSize = 5;
4883       break;
4884     case VariantWildCastle:
4885       pieces = FIDEArray;
4886       /* !!?shuffle with kings guaranteed to be on d or e file */
4887       shuffleOpenings = 1;
4888       break;
4889     case VariantNoCastle:
4890       pieces = FIDEArray;
4891       nrCastlingRights = 0;
4892       /* !!?unconstrained back-rank shuffle */
4893       shuffleOpenings = 1;
4894       break;
4895     }
4896
4897     overrule = 0;
4898     if(appData.NrFiles >= 0) {
4899         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4900         gameInfo.boardWidth = appData.NrFiles;
4901     }
4902     if(appData.NrRanks >= 0) {
4903         gameInfo.boardHeight = appData.NrRanks;
4904     }
4905     if(appData.holdingsSize >= 0) {
4906         i = appData.holdingsSize;
4907         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4908         gameInfo.holdingsSize = i;
4909     }
4910     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4911     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4912         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4913
4914     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4915     if(pawnRow < 1) pawnRow = 1;
4916     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4917
4918     /* User pieceToChar list overrules defaults */
4919     if(appData.pieceToCharTable != NULL)
4920         SetCharTable(pieceToChar, appData.pieceToCharTable);
4921
4922     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4923
4924         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4925             s = (ChessSquare) 0; /* account holding counts in guard band */
4926         for( i=0; i<BOARD_HEIGHT; i++ )
4927             initialPosition[i][j] = s;
4928
4929         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4930         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4931         initialPosition[pawnRow][j] = WhitePawn;
4932         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4933         if(gameInfo.variant == VariantXiangqi) {
4934             if(j&1) {
4935                 initialPosition[pawnRow][j] = 
4936                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4937                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4938                    initialPosition[2][j] = WhiteCannon;
4939                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4940                 }
4941             }
4942         }
4943         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4944     }
4945     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4946
4947             j=BOARD_LEFT+1;
4948             initialPosition[1][j] = WhiteBishop;
4949             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4950             j=BOARD_RGHT-2;
4951             initialPosition[1][j] = WhiteRook;
4952             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4953     }
4954
4955     if( nrCastlingRights == -1) {
4956         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4957         /*       This sets default castling rights from none to normal corners   */
4958         /* Variants with other castling rights must set them themselves above    */
4959         nrCastlingRights = 6;
4960        
4961         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4962         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4963         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4964         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4965         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4966         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4967      }
4968
4969      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4970      if(gameInfo.variant == VariantGreat) { // promotion commoners
4971         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4972         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4973         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4974         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4975      }
4976   if (appData.debugMode) {
4977     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4978   }
4979     if(shuffleOpenings) {
4980         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4981         startedFromSetupPosition = TRUE;
4982     }
4983     if(startedFromPositionFile) {
4984       /* [HGM] loadPos: use PositionFile for every new game */
4985       CopyBoard(initialPosition, filePosition);
4986       for(i=0; i<nrCastlingRights; i++)
4987           initialRights[i] = filePosition[CASTLING][i];
4988       startedFromSetupPosition = TRUE;
4989     }
4990
4991     CopyBoard(boards[0], initialPosition);
4992
4993     if(oldx != gameInfo.boardWidth ||
4994        oldy != gameInfo.boardHeight ||
4995        oldh != gameInfo.holdingsWidth
4996 #ifdef GOTHIC
4997        || oldv == VariantGothic ||        // For licensing popups
4998        gameInfo.variant == VariantGothic
4999 #endif
5000 #ifdef FALCON
5001        || oldv == VariantFalcon ||
5002        gameInfo.variant == VariantFalcon
5003 #endif
5004                                          )
5005             InitDrawingSizes(-2 ,0);
5006
5007     if (redraw)
5008       DrawPosition(TRUE, boards[currentMove]);
5009 }
5010
5011 void
5012 SendBoard(cps, moveNum)
5013      ChessProgramState *cps;
5014      int moveNum;
5015 {
5016     char message[MSG_SIZ];
5017     
5018     if (cps->useSetboard) {
5019       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5020       sprintf(message, "setboard %s\n", fen);
5021       SendToProgram(message, cps);
5022       free(fen);
5023
5024     } else {
5025       ChessSquare *bp;
5026       int i, j;
5027       /* Kludge to set black to move, avoiding the troublesome and now
5028        * deprecated "black" command.
5029        */
5030       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5031
5032       SendToProgram("edit\n", cps);
5033       SendToProgram("#\n", cps);
5034       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5035         bp = &boards[moveNum][i][BOARD_LEFT];
5036         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5037           if ((int) *bp < (int) BlackPawn) {
5038             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5039                     AAA + j, ONE + i);
5040             if(message[0] == '+' || message[0] == '~') {
5041                 sprintf(message, "%c%c%c+\n",
5042                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5043                         AAA + j, ONE + i);
5044             }
5045             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5046                 message[1] = BOARD_RGHT   - 1 - j + '1';
5047                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5048             }
5049             SendToProgram(message, cps);
5050           }
5051         }
5052       }
5053     
5054       SendToProgram("c\n", cps);
5055       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5056         bp = &boards[moveNum][i][BOARD_LEFT];
5057         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5058           if (((int) *bp != (int) EmptySquare)
5059               && ((int) *bp >= (int) BlackPawn)) {
5060             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5061                     AAA + j, ONE + i);
5062             if(message[0] == '+' || message[0] == '~') {
5063                 sprintf(message, "%c%c%c+\n",
5064                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5065                         AAA + j, ONE + i);
5066             }
5067             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5068                 message[1] = BOARD_RGHT   - 1 - j + '1';
5069                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5070             }
5071             SendToProgram(message, cps);
5072           }
5073         }
5074       }
5075     
5076       SendToProgram(".\n", cps);
5077     }
5078     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5079 }
5080
5081 int
5082 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5083 {
5084     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5085     /* [HGM] add Shogi promotions */
5086     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5087     ChessSquare piece;
5088     ChessMove moveType;
5089     Boolean premove;
5090
5091     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5092     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5093
5094     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5095       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5096         return FALSE;
5097
5098     piece = boards[currentMove][fromY][fromX];
5099     if(gameInfo.variant == VariantShogi) {
5100         promotionZoneSize = 3;
5101         highestPromotingPiece = (int)WhiteFerz;
5102     } else if(gameInfo.variant == VariantMakruk) {
5103         promotionZoneSize = 3;
5104     }
5105
5106     // next weed out all moves that do not touch the promotion zone at all
5107     if((int)piece >= BlackPawn) {
5108         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5109              return FALSE;
5110         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5111     } else {
5112         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5113            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5114     }
5115
5116     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5117
5118     // weed out mandatory Shogi promotions
5119     if(gameInfo.variant == VariantShogi) {
5120         if(piece >= BlackPawn) {
5121             if(toY == 0 && piece == BlackPawn ||
5122                toY == 0 && piece == BlackQueen ||
5123                toY <= 1 && piece == BlackKnight) {
5124                 *promoChoice = '+';
5125                 return FALSE;
5126             }
5127         } else {
5128             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5129                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5130                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5131                 *promoChoice = '+';
5132                 return FALSE;
5133             }
5134         }
5135     }
5136
5137     // weed out obviously illegal Pawn moves
5138     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5139         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5140         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5141         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5142         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5143         // note we are not allowed to test for valid (non-)capture, due to premove
5144     }
5145
5146     // we either have a choice what to promote to, or (in Shogi) whether to promote
5147     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5148         *promoChoice = PieceToChar(BlackFerz);  // no choice
5149         return FALSE;
5150     }
5151     if(appData.alwaysPromoteToQueen) { // predetermined
5152         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5153              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5154         else *promoChoice = PieceToChar(BlackQueen);
5155         return FALSE;
5156     }
5157
5158     // suppress promotion popup on illegal moves that are not premoves
5159     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5160               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5161     if(appData.testLegality && !premove) {
5162         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5163                         fromY, fromX, toY, toX, NULLCHAR);
5164         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5165            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5166             return FALSE;
5167     }
5168
5169     return TRUE;
5170 }
5171
5172 int
5173 InPalace(row, column)
5174      int row, column;
5175 {   /* [HGM] for Xiangqi */
5176     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5177          column < (BOARD_WIDTH + 4)/2 &&
5178          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5179     return FALSE;
5180 }
5181
5182 int
5183 PieceForSquare (x, y)
5184      int x;
5185      int y;
5186 {
5187   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5188      return -1;
5189   else
5190      return boards[currentMove][y][x];
5191 }
5192
5193 int
5194 OKToStartUserMove(x, y)
5195      int x, y;
5196 {
5197     ChessSquare from_piece;
5198     int white_piece;
5199
5200     if (matchMode) return FALSE;
5201     if (gameMode == EditPosition) return TRUE;
5202
5203     if (x >= 0 && y >= 0)
5204       from_piece = boards[currentMove][y][x];
5205     else
5206       from_piece = EmptySquare;
5207
5208     if (from_piece == EmptySquare) return FALSE;
5209
5210     white_piece = (int)from_piece >= (int)WhitePawn &&
5211       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5212
5213     switch (gameMode) {
5214       case PlayFromGameFile:
5215       case AnalyzeFile:
5216       case TwoMachinesPlay:
5217       case EndOfGame:
5218         return FALSE;
5219
5220       case IcsObserving:
5221       case IcsIdle:
5222         return FALSE;
5223
5224       case MachinePlaysWhite:
5225       case IcsPlayingBlack:
5226         if (appData.zippyPlay) return FALSE;
5227         if (white_piece) {
5228             DisplayMoveError(_("You are playing Black"));
5229             return FALSE;
5230         }
5231         break;
5232
5233       case MachinePlaysBlack:
5234       case IcsPlayingWhite:
5235         if (appData.zippyPlay) return FALSE;
5236         if (!white_piece) {
5237             DisplayMoveError(_("You are playing White"));
5238             return FALSE;
5239         }
5240         break;
5241
5242       case EditGame:
5243         if (!white_piece && WhiteOnMove(currentMove)) {
5244             DisplayMoveError(_("It is White's turn"));
5245             return FALSE;
5246         }           
5247         if (white_piece && !WhiteOnMove(currentMove)) {
5248             DisplayMoveError(_("It is Black's turn"));
5249             return FALSE;
5250         }           
5251         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5252             /* Editing correspondence game history */
5253             /* Could disallow this or prompt for confirmation */
5254             cmailOldMove = -1;
5255         }
5256         break;
5257
5258       case BeginningOfGame:
5259         if (appData.icsActive) return FALSE;
5260         if (!appData.noChessProgram) {
5261             if (!white_piece) {
5262                 DisplayMoveError(_("You are playing White"));
5263                 return FALSE;
5264             }
5265         }
5266         break;
5267         
5268       case Training:
5269         if (!white_piece && WhiteOnMove(currentMove)) {
5270             DisplayMoveError(_("It is White's turn"));
5271             return FALSE;
5272         }           
5273         if (white_piece && !WhiteOnMove(currentMove)) {
5274             DisplayMoveError(_("It is Black's turn"));
5275             return FALSE;
5276         }           
5277         break;
5278
5279       default:
5280       case IcsExamining:
5281         break;
5282     }
5283     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5284         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5285         && gameMode != AnalyzeFile && gameMode != Training) {
5286         DisplayMoveError(_("Displayed position is not current"));
5287         return FALSE;
5288     }
5289     return TRUE;
5290 }
5291
5292 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5293 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5294 int lastLoadGameUseList = FALSE;
5295 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5296 ChessMove lastLoadGameStart = (ChessMove) 0;
5297
5298 ChessMove
5299 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5300      int fromX, fromY, toX, toY;
5301      int promoChar;
5302      Boolean captureOwn;
5303 {
5304     ChessMove moveType;
5305     ChessSquare pdown, pup;
5306
5307     /* Check if the user is playing in turn.  This is complicated because we
5308        let the user "pick up" a piece before it is his turn.  So the piece he
5309        tried to pick up may have been captured by the time he puts it down!
5310        Therefore we use the color the user is supposed to be playing in this
5311        test, not the color of the piece that is currently on the starting
5312        square---except in EditGame mode, where the user is playing both
5313        sides; fortunately there the capture race can't happen.  (It can
5314        now happen in IcsExamining mode, but that's just too bad.  The user
5315        will get a somewhat confusing message in that case.)
5316        */
5317
5318     switch (gameMode) {
5319       case PlayFromGameFile:
5320       case AnalyzeFile:
5321       case TwoMachinesPlay:
5322       case EndOfGame:
5323       case IcsObserving:
5324       case IcsIdle:
5325         /* We switched into a game mode where moves are not accepted,
5326            perhaps while the mouse button was down. */
5327         return ImpossibleMove;
5328
5329       case MachinePlaysWhite:
5330         /* User is moving for Black */
5331         if (WhiteOnMove(currentMove)) {
5332             DisplayMoveError(_("It is White's turn"));
5333             return ImpossibleMove;
5334         }
5335         break;
5336
5337       case MachinePlaysBlack:
5338         /* User is moving for White */
5339         if (!WhiteOnMove(currentMove)) {
5340             DisplayMoveError(_("It is Black's turn"));
5341             return ImpossibleMove;
5342         }
5343         break;
5344
5345       case EditGame:
5346       case IcsExamining:
5347       case BeginningOfGame:
5348       case AnalyzeMode:
5349       case Training:
5350         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5351             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5352             /* User is moving for Black */
5353             if (WhiteOnMove(currentMove)) {
5354                 DisplayMoveError(_("It is White's turn"));
5355                 return ImpossibleMove;
5356             }
5357         } else {
5358             /* User is moving for White */
5359             if (!WhiteOnMove(currentMove)) {
5360                 DisplayMoveError(_("It is Black's turn"));
5361                 return ImpossibleMove;
5362             }
5363         }
5364         break;
5365
5366       case IcsPlayingBlack:
5367         /* User is moving for Black */
5368         if (WhiteOnMove(currentMove)) {
5369             if (!appData.premove) {
5370                 DisplayMoveError(_("It is White's turn"));
5371             } else if (toX >= 0 && toY >= 0) {
5372                 premoveToX = toX;
5373                 premoveToY = toY;
5374                 premoveFromX = fromX;
5375                 premoveFromY = fromY;
5376                 premovePromoChar = promoChar;
5377                 gotPremove = 1;
5378                 if (appData.debugMode) 
5379                     fprintf(debugFP, "Got premove: fromX %d,"
5380                             "fromY %d, toX %d, toY %d\n",
5381                             fromX, fromY, toX, toY);
5382             }
5383             return ImpossibleMove;
5384         }
5385         break;
5386
5387       case IcsPlayingWhite:
5388         /* User is moving for White */
5389         if (!WhiteOnMove(currentMove)) {
5390             if (!appData.premove) {
5391                 DisplayMoveError(_("It is Black's turn"));
5392             } else if (toX >= 0 && toY >= 0) {
5393                 premoveToX = toX;
5394                 premoveToY = toY;
5395                 premoveFromX = fromX;
5396                 premoveFromY = fromY;
5397                 premovePromoChar = promoChar;
5398                 gotPremove = 1;
5399                 if (appData.debugMode) 
5400                     fprintf(debugFP, "Got premove: fromX %d,"
5401                             "fromY %d, toX %d, toY %d\n",
5402                             fromX, fromY, toX, toY);
5403             }
5404             return ImpossibleMove;
5405         }
5406         break;
5407
5408       default:
5409         break;
5410
5411       case EditPosition:
5412         /* EditPosition, empty square, or different color piece;
5413            click-click move is possible */
5414         if (toX == -2 || toY == -2) {
5415             boards[0][fromY][fromX] = EmptySquare;
5416             return AmbiguousMove;
5417         } else if (toX >= 0 && toY >= 0) {
5418             boards[0][toY][toX] = boards[0][fromY][fromX];
5419             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5420                 if(boards[0][fromY][0] != EmptySquare) {
5421                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5422                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5423                 }
5424             } else
5425             if(fromX == BOARD_RGHT+1) {
5426                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5427                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5428                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5429                 }
5430             } else
5431             boards[0][fromY][fromX] = EmptySquare;
5432             return AmbiguousMove;
5433         }
5434         return ImpossibleMove;
5435     }
5436
5437     if(toX < 0 || toY < 0) return ImpossibleMove;
5438     pdown = boards[currentMove][fromY][fromX];
5439     pup = boards[currentMove][toY][toX];
5440
5441     /* [HGM] If move started in holdings, it means a drop */
5442     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5443          if( pup != EmptySquare ) return ImpossibleMove;
5444          if(appData.testLegality) {
5445              /* it would be more logical if LegalityTest() also figured out
5446               * which drops are legal. For now we forbid pawns on back rank.
5447               * Shogi is on its own here...
5448               */
5449              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5450                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5451                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5452          }
5453          return WhiteDrop; /* Not needed to specify white or black yet */
5454     }
5455
5456     userOfferedDraw = FALSE;
5457         
5458     /* [HGM] always test for legality, to get promotion info */
5459     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5460                                          fromY, fromX, toY, toX, promoChar);
5461     /* [HGM] but possibly ignore an IllegalMove result */
5462     if (appData.testLegality) {
5463         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5464             DisplayMoveError(_("Illegal move"));
5465             return ImpossibleMove;
5466         }
5467     }
5468
5469     return moveType;
5470     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5471        function is made into one that returns an OK move type if FinishMove
5472        should be called. This to give the calling driver routine the
5473        opportunity to finish the userMove input with a promotion popup,
5474        without bothering the user with this for invalid or illegal moves */
5475
5476 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5477 }
5478
5479 /* Common tail of UserMoveEvent and DropMenuEvent */
5480 int
5481 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5482      ChessMove moveType;
5483      int fromX, fromY, toX, toY;
5484      /*char*/int promoChar;
5485 {
5486     char *bookHit = 0;
5487
5488     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5489         // [HGM] superchess: suppress promotions to non-available piece
5490         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5491         if(WhiteOnMove(currentMove)) {
5492             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5493         } else {
5494             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5495         }
5496     }
5497
5498     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5499        move type in caller when we know the move is a legal promotion */
5500     if(moveType == NormalMove && promoChar)
5501         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5502
5503     /* [HGM] convert drag-and-drop piece drops to standard form */
5504     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5505          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5506            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5507                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5508            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5509            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5510            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5511            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5512          fromY = DROP_RANK;
5513     }
5514
5515     /* [HGM] <popupFix> The following if has been moved here from
5516        UserMoveEvent(). Because it seemed to belong here (why not allow
5517        piece drops in training games?), and because it can only be
5518        performed after it is known to what we promote. */
5519     if (gameMode == Training) {
5520       /* compare the move played on the board to the next move in the
5521        * game. If they match, display the move and the opponent's response. 
5522        * If they don't match, display an error message.
5523        */
5524       int saveAnimate;
5525       Board testBoard;
5526       CopyBoard(testBoard, boards[currentMove]);
5527       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5528
5529       if (CompareBoards(testBoard, boards[currentMove+1])) {
5530         ForwardInner(currentMove+1);
5531
5532         /* Autoplay the opponent's response.
5533          * if appData.animate was TRUE when Training mode was entered,
5534          * the response will be animated.
5535          */
5536         saveAnimate = appData.animate;
5537         appData.animate = animateTraining;
5538         ForwardInner(currentMove+1);
5539         appData.animate = saveAnimate;
5540
5541         /* check for the end of the game */
5542         if (currentMove >= forwardMostMove) {
5543           gameMode = PlayFromGameFile;
5544           ModeHighlight();
5545           SetTrainingModeOff();
5546           DisplayInformation(_("End of game"));
5547         }
5548       } else {
5549         DisplayError(_("Incorrect move"), 0);
5550       }
5551       return 1;
5552     }
5553
5554   /* Ok, now we know that the move is good, so we can kill
5555      the previous line in Analysis Mode */
5556   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5557                                 && currentMove < forwardMostMove) {
5558     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5559   }
5560
5561   /* If we need the chess program but it's dead, restart it */
5562   ResurrectChessProgram();
5563
5564   /* A user move restarts a paused game*/
5565   if (pausing)
5566     PauseEvent();
5567
5568   thinkOutput[0] = NULLCHAR;
5569
5570   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5571
5572   if (gameMode == BeginningOfGame) {
5573     if (appData.noChessProgram) {
5574       gameMode = EditGame;
5575       SetGameInfo();
5576     } else {
5577       char buf[MSG_SIZ];
5578       gameMode = MachinePlaysBlack;
5579       StartClocks();
5580       SetGameInfo();
5581       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5582       DisplayTitle(buf);
5583       if (first.sendName) {
5584         sprintf(buf, "name %s\n", gameInfo.white);
5585         SendToProgram(buf, &first);
5586       }
5587       StartClocks();
5588     }
5589     ModeHighlight();
5590   }
5591
5592   /* Relay move to ICS or chess engine */
5593   if (appData.icsActive) {
5594     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5595         gameMode == IcsExamining) {
5596       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5597       ics_user_moved = 1;
5598     }
5599   } else {
5600     if (first.sendTime && (gameMode == BeginningOfGame ||
5601                            gameMode == MachinePlaysWhite ||
5602                            gameMode == MachinePlaysBlack)) {
5603       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5604     }
5605     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5606          // [HGM] book: if program might be playing, let it use book
5607         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5608         first.maybeThinking = TRUE;
5609     } else SendMoveToProgram(forwardMostMove-1, &first);
5610     if (currentMove == cmailOldMove + 1) {
5611       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5612     }
5613   }
5614
5615   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5616
5617   switch (gameMode) {
5618   case EditGame:
5619     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5620     case MT_NONE:
5621     case MT_CHECK:
5622       break;
5623     case MT_CHECKMATE:
5624     case MT_STAINMATE:
5625       if (WhiteOnMove(currentMove)) {
5626         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5627       } else {
5628         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5629       }
5630       break;
5631     case MT_STALEMATE:
5632       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5633       break;
5634     }
5635     break;
5636     
5637   case MachinePlaysBlack:
5638   case MachinePlaysWhite:
5639     /* disable certain menu options while machine is thinking */
5640     SetMachineThinkingEnables();
5641     break;
5642
5643   default:
5644     break;
5645   }
5646
5647   if(bookHit) { // [HGM] book: simulate book reply
5648         static char bookMove[MSG_SIZ]; // a bit generous?
5649
5650         programStats.nodes = programStats.depth = programStats.time = 
5651         programStats.score = programStats.got_only_move = 0;
5652         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5653
5654         strcpy(bookMove, "move ");
5655         strcat(bookMove, bookHit);
5656         HandleMachineMove(bookMove, &first);
5657   }
5658   return 1;
5659 }
5660
5661 void
5662 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5663      int fromX, fromY, toX, toY;
5664      int promoChar;
5665 {
5666     /* [HGM] This routine was added to allow calling of its two logical
5667        parts from other modules in the old way. Before, UserMoveEvent()
5668        automatically called FinishMove() if the move was OK, and returned
5669        otherwise. I separated the two, in order to make it possible to
5670        slip a promotion popup in between. But that it always needs two
5671        calls, to the first part, (now called UserMoveTest() ), and to
5672        FinishMove if the first part succeeded. Calls that do not need
5673        to do anything in between, can call this routine the old way. 
5674     */
5675     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5676 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5677     if(moveType == AmbiguousMove)
5678         DrawPosition(FALSE, boards[currentMove]);
5679     else if(moveType != ImpossibleMove && moveType != Comment)
5680         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5681 }
5682
5683 void
5684 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5685      Board board;
5686      int flags;
5687      ChessMove kind;
5688      int rf, ff, rt, ft;
5689      VOIDSTAR closure;
5690 {
5691     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5692     Markers *m = (Markers *) closure;
5693     if(rf == fromY && ff == fromX)
5694         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5695                          || kind == WhiteCapturesEnPassant
5696                          || kind == BlackCapturesEnPassant);
5697     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5698 }
5699
5700 void
5701 MarkTargetSquares(int clear)
5702 {
5703   int x, y;
5704   if(!appData.markers || !appData.highlightDragging || 
5705      !appData.testLegality || gameMode == EditPosition) return;
5706   if(clear) {
5707     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5708   } else {
5709     int capt = 0;
5710     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5711     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5712       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5713       if(capt)
5714       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5715     }
5716   }
5717   DrawPosition(TRUE, NULL);
5718 }
5719
5720 void LeftClick(ClickType clickType, int xPix, int yPix)
5721 {
5722     int x, y;
5723     Boolean saveAnimate;
5724     static int second = 0, promotionChoice = 0;
5725     char promoChoice = NULLCHAR;
5726
5727     if (clickType == Press) ErrorPopDown();
5728     MarkTargetSquares(1);
5729
5730     x = EventToSquare(xPix, BOARD_WIDTH);
5731     y = EventToSquare(yPix, BOARD_HEIGHT);
5732     if (!flipView && y >= 0) {
5733         y = BOARD_HEIGHT - 1 - y;
5734     }
5735     if (flipView && x >= 0) {
5736         x = BOARD_WIDTH - 1 - x;
5737     }
5738
5739     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5740         if(clickType == Release) return; // ignore upclick of click-click destination
5741         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5742         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5743         if(gameInfo.holdingsWidth && 
5744                 (WhiteOnMove(currentMove) 
5745                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5746                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5747             // click in right holdings, for determining promotion piece
5748             ChessSquare p = boards[currentMove][y][x];
5749             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5750             if(p != EmptySquare) {
5751                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5752                 fromX = fromY = -1;
5753                 return;
5754             }
5755         }
5756         DrawPosition(FALSE, boards[currentMove]);
5757         return;
5758     }
5759
5760     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5761     if(clickType == Press
5762             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5763               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5764               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5765         return;
5766
5767     if (fromX == -1) {
5768         if (clickType == Press) {
5769             /* First square */
5770             if (OKToStartUserMove(x, y)) {
5771                 fromX = x;
5772                 fromY = y;
5773                 second = 0;
5774                 MarkTargetSquares(0);
5775                 DragPieceBegin(xPix, yPix);
5776                 if (appData.highlightDragging) {
5777                     SetHighlights(x, y, -1, -1);
5778                 }
5779             }
5780         }
5781         return;
5782     }
5783
5784     /* fromX != -1 */
5785     if (clickType == Press && gameMode != EditPosition) {
5786         ChessSquare fromP;
5787         ChessSquare toP;
5788         int frc;
5789
5790         // ignore off-board to clicks
5791         if(y < 0 || x < 0) return;
5792
5793         /* Check if clicking again on the same color piece */
5794         fromP = boards[currentMove][fromY][fromX];
5795         toP = boards[currentMove][y][x];
5796         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5797         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5798              WhitePawn <= toP && toP <= WhiteKing &&
5799              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5800              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5801             (BlackPawn <= fromP && fromP <= BlackKing && 
5802              BlackPawn <= toP && toP <= BlackKing &&
5803              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5804              !(fromP == BlackKing && toP == BlackRook && frc))) {
5805             /* Clicked again on same color piece -- changed his mind */
5806             second = (x == fromX && y == fromY);
5807             if (appData.highlightDragging) {
5808                 SetHighlights(x, y, -1, -1);
5809             } else {
5810                 ClearHighlights();
5811             }
5812             if (OKToStartUserMove(x, y)) {
5813                 fromX = x;
5814                 fromY = y;
5815                 MarkTargetSquares(0);
5816                 DragPieceBegin(xPix, yPix);
5817             }
5818             return;
5819         }
5820         // ignore clicks on holdings
5821         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5822     }
5823
5824     if (clickType == Release && x == fromX && y == fromY) {
5825         DragPieceEnd(xPix, yPix);
5826         if (appData.animateDragging) {
5827             /* Undo animation damage if any */
5828             DrawPosition(FALSE, NULL);
5829         }
5830         if (second) {
5831             /* Second up/down in same square; just abort move */
5832             second = 0;
5833             fromX = fromY = -1;
5834             ClearHighlights();
5835             gotPremove = 0;
5836             ClearPremoveHighlights();
5837         } else {
5838             /* First upclick in same square; start click-click mode */
5839             SetHighlights(x, y, -1, -1);
5840         }
5841         return;
5842     }
5843
5844     /* we now have a different from- and (possibly off-board) to-square */
5845     /* Completed move */
5846     toX = x;
5847     toY = y;
5848     saveAnimate = appData.animate;
5849     if (clickType == Press) {
5850         /* Finish clickclick move */
5851         if (appData.animate || appData.highlightLastMove) {
5852             SetHighlights(fromX, fromY, toX, toY);
5853         } else {
5854             ClearHighlights();
5855         }
5856     } else {
5857         /* Finish drag move */
5858         if (appData.highlightLastMove) {
5859             SetHighlights(fromX, fromY, toX, toY);
5860         } else {
5861             ClearHighlights();
5862         }
5863         DragPieceEnd(xPix, yPix);
5864         /* Don't animate move and drag both */
5865         appData.animate = FALSE;
5866     }
5867
5868     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5869     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5870         ChessSquare piece = boards[currentMove][fromY][fromX];
5871         if(gameMode == EditPosition && piece != EmptySquare &&
5872            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5873             int n;
5874              
5875             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5876                 n = PieceToNumber(piece - (int)BlackPawn);
5877                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5878                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5879                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5880             } else
5881             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5882                 n = PieceToNumber(piece);
5883                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5884                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5885                 boards[currentMove][n][BOARD_WIDTH-2]++;
5886             }
5887             boards[currentMove][fromY][fromX] = EmptySquare;
5888         }
5889         ClearHighlights();
5890         fromX = fromY = -1;
5891         DrawPosition(TRUE, boards[currentMove]);
5892         return;
5893     }
5894
5895     // off-board moves should not be highlighted
5896     if(x < 0 || x < 0) ClearHighlights();
5897
5898     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5899         SetHighlights(fromX, fromY, toX, toY);
5900         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5901             // [HGM] super: promotion to captured piece selected from holdings
5902             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5903             promotionChoice = TRUE;
5904             // kludge follows to temporarily execute move on display, without promoting yet
5905             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5906             boards[currentMove][toY][toX] = p;
5907             DrawPosition(FALSE, boards[currentMove]);
5908             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5909             boards[currentMove][toY][toX] = q;
5910             DisplayMessage("Click in holdings to choose piece", "");
5911             return;
5912         }
5913         PromotionPopUp();
5914     } else {
5915         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5916         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5917         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5918         fromX = fromY = -1;
5919     }
5920     appData.animate = saveAnimate;
5921     if (appData.animate || appData.animateDragging) {
5922         /* Undo animation damage if needed */
5923         DrawPosition(FALSE, NULL);
5924     }
5925 }
5926
5927 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5928 {
5929 //    char * hint = lastHint;
5930     FrontEndProgramStats stats;
5931
5932     stats.which = cps == &first ? 0 : 1;
5933     stats.depth = cpstats->depth;
5934     stats.nodes = cpstats->nodes;
5935     stats.score = cpstats->score;
5936     stats.time = cpstats->time;
5937     stats.pv = cpstats->movelist;
5938     stats.hint = lastHint;
5939     stats.an_move_index = 0;
5940     stats.an_move_count = 0;
5941
5942     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5943         stats.hint = cpstats->move_name;
5944         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5945         stats.an_move_count = cpstats->nr_moves;
5946     }
5947
5948     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5949
5950     SetProgramStats( &stats );
5951 }
5952
5953 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5954 {   // [HGM] book: this routine intercepts moves to simulate book replies
5955     char *bookHit = NULL;
5956
5957     //first determine if the incoming move brings opponent into his book
5958     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5959         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5960     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5961     if(bookHit != NULL && !cps->bookSuspend) {
5962         // make sure opponent is not going to reply after receiving move to book position
5963         SendToProgram("force\n", cps);
5964         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5965     }
5966     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5967     // now arrange restart after book miss
5968     if(bookHit) {
5969         // after a book hit we never send 'go', and the code after the call to this routine
5970         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5971         char buf[MSG_SIZ];
5972         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5973         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5974         SendToProgram(buf, cps);
5975         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5976     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5977         SendToProgram("go\n", cps);
5978         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5979     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5980         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5981             SendToProgram("go\n", cps); 
5982         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5983     }
5984     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5985 }
5986
5987 char *savedMessage;
5988 ChessProgramState *savedState;
5989 void DeferredBookMove(void)
5990 {
5991         if(savedState->lastPing != savedState->lastPong)
5992                     ScheduleDelayedEvent(DeferredBookMove, 10);
5993         else
5994         HandleMachineMove(savedMessage, savedState);
5995 }
5996
5997 void
5998 HandleMachineMove(message, cps)
5999      char *message;
6000      ChessProgramState *cps;
6001 {
6002     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6003     char realname[MSG_SIZ];
6004     int fromX, fromY, toX, toY;
6005     ChessMove moveType;
6006     char promoChar;
6007     char *p;
6008     int machineWhite;
6009     char *bookHit;
6010
6011     cps->userError = 0;
6012
6013 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6014     /*
6015      * Kludge to ignore BEL characters
6016      */
6017     while (*message == '\007') message++;
6018
6019     /*
6020      * [HGM] engine debug message: ignore lines starting with '#' character
6021      */
6022     if(cps->debug && *message == '#') return;
6023
6024     /*
6025      * Look for book output
6026      */
6027     if (cps == &first && bookRequested) {
6028         if (message[0] == '\t' || message[0] == ' ') {
6029             /* Part of the book output is here; append it */
6030             strcat(bookOutput, message);
6031             strcat(bookOutput, "  \n");
6032             return;
6033         } else if (bookOutput[0] != NULLCHAR) {
6034             /* All of book output has arrived; display it */
6035             char *p = bookOutput;
6036             while (*p != NULLCHAR) {
6037                 if (*p == '\t') *p = ' ';
6038                 p++;
6039             }
6040             DisplayInformation(bookOutput);
6041             bookRequested = FALSE;
6042             /* Fall through to parse the current output */
6043         }
6044     }
6045
6046     /*
6047      * Look for machine move.
6048      */
6049     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6050         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6051     {
6052         /* This method is only useful on engines that support ping */
6053         if (cps->lastPing != cps->lastPong) {
6054           if (gameMode == BeginningOfGame) {
6055             /* Extra move from before last new; ignore */
6056             if (appData.debugMode) {
6057                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6058             }
6059           } else {
6060             if (appData.debugMode) {
6061                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6062                         cps->which, gameMode);
6063             }
6064
6065             SendToProgram("undo\n", cps);
6066           }
6067           return;
6068         }
6069
6070         switch (gameMode) {
6071           case BeginningOfGame:
6072             /* Extra move from before last reset; ignore */
6073             if (appData.debugMode) {
6074                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6075             }
6076             return;
6077
6078           case EndOfGame:
6079           case IcsIdle:
6080           default:
6081             /* Extra move after we tried to stop.  The mode test is
6082                not a reliable way of detecting this problem, but it's
6083                the best we can do on engines that don't support ping.
6084             */
6085             if (appData.debugMode) {
6086                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6087                         cps->which, gameMode);
6088             }
6089             SendToProgram("undo\n", cps);
6090             return;
6091
6092           case MachinePlaysWhite:
6093           case IcsPlayingWhite:
6094             machineWhite = TRUE;
6095             break;
6096
6097           case MachinePlaysBlack:
6098           case IcsPlayingBlack:
6099             machineWhite = FALSE;
6100             break;
6101
6102           case TwoMachinesPlay:
6103             machineWhite = (cps->twoMachinesColor[0] == 'w');
6104             break;
6105         }
6106         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6107             if (appData.debugMode) {
6108                 fprintf(debugFP,
6109                         "Ignoring move out of turn by %s, gameMode %d"
6110                         ", forwardMost %d\n",
6111                         cps->which, gameMode, forwardMostMove);
6112             }
6113             return;
6114         }
6115
6116     if (appData.debugMode) { int f = forwardMostMove;
6117         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6118                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6119                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6120     }
6121         if(cps->alphaRank) AlphaRank(machineMove, 4);
6122         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6123                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6124             /* Machine move could not be parsed; ignore it. */
6125             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6126                     machineMove, cps->which);
6127             DisplayError(buf1, 0);
6128             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6129                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6130             if (gameMode == TwoMachinesPlay) {
6131               GameEnds(machineWhite ? BlackWins : WhiteWins,
6132                        buf1, GE_XBOARD);
6133             }
6134             return;
6135         }
6136
6137         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6138         /* So we have to redo legality test with true e.p. status here,  */
6139         /* to make sure an illegal e.p. capture does not slip through,   */
6140         /* to cause a forfeit on a justified illegal-move complaint      */
6141         /* of the opponent.                                              */
6142         if( gameMode==TwoMachinesPlay && appData.testLegality
6143             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6144                                                               ) {
6145            ChessMove moveType;
6146            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6147                              fromY, fromX, toY, toX, promoChar);
6148             if (appData.debugMode) {
6149                 int i;
6150                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6151                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6152                 fprintf(debugFP, "castling rights\n");
6153             }
6154             if(moveType == IllegalMove) {
6155                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6156                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6157                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6158                            buf1, GE_XBOARD);
6159                 return;
6160            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6161            /* [HGM] Kludge to handle engines that send FRC-style castling
6162               when they shouldn't (like TSCP-Gothic) */
6163            switch(moveType) {
6164              case WhiteASideCastleFR:
6165              case BlackASideCastleFR:
6166                toX+=2;
6167                currentMoveString[2]++;
6168                break;
6169              case WhiteHSideCastleFR:
6170              case BlackHSideCastleFR:
6171                toX--;
6172                currentMoveString[2]--;
6173                break;
6174              default: ; // nothing to do, but suppresses warning of pedantic compilers
6175            }
6176         }
6177         hintRequested = FALSE;
6178         lastHint[0] = NULLCHAR;
6179         bookRequested = FALSE;
6180         /* Program may be pondering now */
6181         cps->maybeThinking = TRUE;
6182         if (cps->sendTime == 2) cps->sendTime = 1;
6183         if (cps->offeredDraw) cps->offeredDraw--;
6184
6185 #if ZIPPY
6186         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6187             first.initDone) {
6188           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6189           ics_user_moved = 1;
6190           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6191                 char buf[3*MSG_SIZ];
6192
6193                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6194                         programStats.score / 100.,
6195                         programStats.depth,
6196                         programStats.time / 100.,
6197                         (unsigned int)programStats.nodes,
6198                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6199                         programStats.movelist);
6200                 SendToICS(buf);
6201 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6202           }
6203         }
6204 #endif
6205         /* currentMoveString is set as a side-effect of ParseOneMove */
6206         strcpy(machineMove, currentMoveString);
6207         strcat(machineMove, "\n");
6208         strcpy(moveList[forwardMostMove], machineMove);
6209
6210         /* [AS] Save move info and clear stats for next move */
6211         pvInfoList[ forwardMostMove ].score = programStats.score;
6212         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6213         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6214         ClearProgramStats();
6215         thinkOutput[0] = NULLCHAR;
6216         hiddenThinkOutputState = 0;
6217
6218         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6219
6220         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6221         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6222             int count = 0;
6223
6224             while( count < adjudicateLossPlies ) {
6225                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6226
6227                 if( count & 1 ) {
6228                     score = -score; /* Flip score for winning side */
6229                 }
6230
6231                 if( score > adjudicateLossThreshold ) {
6232                     break;
6233                 }
6234
6235                 count++;
6236             }
6237
6238             if( count >= adjudicateLossPlies ) {
6239                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6240
6241                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6242                     "Xboard adjudication", 
6243                     GE_XBOARD );
6244
6245                 return;
6246             }
6247         }
6248
6249         if( gameMode == TwoMachinesPlay ) {
6250           // [HGM] some adjudications useful with buggy engines
6251             int k, count = 0; static int bare = 1;
6252           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6253
6254
6255             if( appData.testLegality )
6256             {   /* [HGM] Some more adjudications for obstinate engines */
6257                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6258                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6259                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6260                 static int moveCount = 6;
6261                 ChessMove result;
6262                 char *reason = NULL;
6263
6264                 /* Count what is on board. */
6265                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6266                 {   ChessSquare p = boards[forwardMostMove][i][j];
6267                     int m=i;
6268
6269                     switch((int) p)
6270                     {   /* count B,N,R and other of each side */
6271                         case WhiteKing:
6272                         case BlackKing:
6273                              NrK++; break; // [HGM] atomic: count Kings
6274                         case WhiteKnight:
6275                              NrWN++; break;
6276                         case WhiteBishop:
6277                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6278                              bishopsColor |= 1 << ((i^j)&1);
6279                              NrWB++; break;
6280                         case BlackKnight:
6281                              NrBN++; break;
6282                         case BlackBishop:
6283                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6284                              bishopsColor |= 1 << ((i^j)&1);
6285                              NrBB++; break;
6286                         case WhiteRook:
6287                              NrWR++; break;
6288                         case BlackRook:
6289                              NrBR++; break;
6290                         case WhiteQueen:
6291                              NrWQ++; break;
6292                         case BlackQueen:
6293                              NrBQ++; break;
6294                         case EmptySquare: 
6295                              break;
6296                         case BlackPawn:
6297                              m = 7-i;
6298                         case WhitePawn:
6299                              PawnAdvance += m; NrPawns++;
6300                     }
6301                     NrPieces += (p != EmptySquare);
6302                     NrW += ((int)p < (int)BlackPawn);
6303                     if(gameInfo.variant == VariantXiangqi && 
6304                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6305                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6306                         NrW -= ((int)p < (int)BlackPawn);
6307                     }
6308                 }
6309
6310                 /* Some material-based adjudications that have to be made before stalemate test */
6311                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6312                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6313                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6314                      if(appData.checkMates) {
6315                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6316                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6317                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6318                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6319                          return;
6320                      }
6321                 }
6322
6323                 /* Bare King in Shatranj (loses) or Losers (wins) */
6324                 if( NrW == 1 || NrPieces - NrW == 1) {
6325                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6326                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6327                      if(appData.checkMates) {
6328                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6329                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6330                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6331                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6332                          return;
6333                      }
6334                   } else
6335                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6336                   {    /* bare King */
6337                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6338                         if(appData.checkMates) {
6339                             /* but only adjudicate if adjudication enabled */
6340                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6341                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6342                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6343                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6344                             return;
6345                         }
6346                   }
6347                 } else bare = 1;
6348
6349
6350             // don't wait for engine to announce game end if we can judge ourselves
6351             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6352               case MT_CHECK:
6353                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6354                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6355                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6356                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6357                             checkCnt++;
6358                         if(checkCnt >= 2) {
6359                             reason = "Xboard adjudication: 3rd check";
6360                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6361                             break;
6362                         }
6363                     }
6364                 }
6365               case MT_NONE:
6366               default:
6367                 break;
6368               case MT_STALEMATE:
6369               case MT_STAINMATE:
6370                 reason = "Xboard adjudication: Stalemate";
6371                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6372                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6373                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6374                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6375                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6376                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6377                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6378                                                                         EP_CHECKMATE : EP_WINS);
6379                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6380                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6381                 }
6382                 break;
6383               case MT_CHECKMATE:
6384                 reason = "Xboard adjudication: Checkmate";
6385                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6386                 break;
6387             }
6388
6389                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6390                     case EP_STALEMATE:
6391                         result = GameIsDrawn; break;
6392                     case EP_CHECKMATE:
6393                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6394                     case EP_WINS:
6395                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6396                     default:
6397                         result = (ChessMove) 0;
6398                 }
6399                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6400                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6401                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6402                     GameEnds( result, reason, GE_XBOARD );
6403                     return;
6404                 }
6405
6406                 /* Next absolutely insufficient mating material. */
6407                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6408                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6409                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6410                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6411                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6412
6413                      /* always flag draws, for judging claims */
6414                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6415
6416                      if(appData.materialDraws) {
6417                          /* but only adjudicate them if adjudication enabled */
6418                          SendToProgram("force\n", cps->other); // suppress reply
6419                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6420                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6421                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6422                          return;
6423                      }
6424                 }
6425
6426                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6427                 if(NrPieces == 4 && 
6428                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6429                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6430                    || NrWN==2 || NrBN==2     /* KNNK */
6431                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6432                   ) ) {
6433                      if(--moveCount < 0 && appData.trivialDraws)
6434                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6435                           SendToProgram("force\n", cps->other); // suppress reply
6436                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6437                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6438                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6439                           return;
6440                      }
6441                 } else moveCount = 6;
6442             }
6443           }
6444           
6445           if (appData.debugMode) { int i;
6446             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6447                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6448                     appData.drawRepeats);
6449             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6450               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6451             
6452           }
6453
6454                 /* Check for rep-draws */
6455                 count = 0;
6456                 for(k = forwardMostMove-2;
6457                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6458                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6459                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6460                     k-=2)
6461                 {   int rights=0;
6462                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6463                         /* compare castling rights */
6464                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6465                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6466                                 rights++; /* King lost rights, while rook still had them */
6467                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6468                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6469                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6470                                    rights++; /* but at least one rook lost them */
6471                         }
6472                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6473                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6474                                 rights++; 
6475                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6476                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6477                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6478                                    rights++;
6479                         }
6480                         if( rights == 0 && ++count > appData.drawRepeats-2
6481                             && appData.drawRepeats > 1) {
6482                              /* adjudicate after user-specified nr of repeats */
6483                              SendToProgram("force\n", cps->other); // suppress reply
6484                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6485                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6486                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6487                                 // [HGM] xiangqi: check for forbidden perpetuals
6488                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6489                                 for(m=forwardMostMove; m>k; m-=2) {
6490                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6491                                         ourPerpetual = 0; // the current mover did not always check
6492                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6493                                         hisPerpetual = 0; // the opponent did not always check
6494                                 }
6495                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6496                                                                         ourPerpetual, hisPerpetual);
6497                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6498                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6499                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6500                                     return;
6501                                 }
6502                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6503                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6504                                 // Now check for perpetual chases
6505                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6506                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6507                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6508                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6509                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6510                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6511                                         return;
6512                                     }
6513                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6514                                         break; // Abort repetition-checking loop.
6515                                 }
6516                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6517                              }
6518                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6519                              return;
6520                         }
6521                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6522                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6523                     }
6524                 }
6525
6526                 /* Now we test for 50-move draws. Determine ply count */
6527                 count = forwardMostMove;
6528                 /* look for last irreversble move */
6529                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6530                     count--;
6531                 /* if we hit starting position, add initial plies */
6532                 if( count == backwardMostMove )
6533                     count -= initialRulePlies;
6534                 count = forwardMostMove - count; 
6535                 if( count >= 100)
6536                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6537                          /* this is used to judge if draw claims are legal */
6538                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6539                          SendToProgram("force\n", cps->other); // suppress reply
6540                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6541                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6542                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6543                          return;
6544                 }
6545
6546                 /* if draw offer is pending, treat it as a draw claim
6547                  * when draw condition present, to allow engines a way to
6548                  * claim draws before making their move to avoid a race
6549                  * condition occurring after their move
6550                  */
6551                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6552                          char *p = NULL;
6553                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6554                              p = "Draw claim: 50-move rule";
6555                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6556                              p = "Draw claim: 3-fold repetition";
6557                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6558                              p = "Draw claim: insufficient mating material";
6559                          if( p != NULL ) {
6560                              SendToProgram("force\n", cps->other); // suppress reply
6561                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6562                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6563                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6564                              return;
6565                          }
6566                 }
6567
6568
6569                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6570                     SendToProgram("force\n", cps->other); // suppress reply
6571                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6572                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6573
6574                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6575
6576                     return;
6577                 }
6578         }
6579
6580         bookHit = NULL;
6581         if (gameMode == TwoMachinesPlay) {
6582             /* [HGM] relaying draw offers moved to after reception of move */
6583             /* and interpreting offer as claim if it brings draw condition */
6584             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6585                 SendToProgram("draw\n", cps->other);
6586             }
6587             if (cps->other->sendTime) {
6588                 SendTimeRemaining(cps->other,
6589                                   cps->other->twoMachinesColor[0] == 'w');
6590             }
6591             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6592             if (firstMove && !bookHit) {
6593                 firstMove = FALSE;
6594                 if (cps->other->useColors) {
6595                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6596                 }
6597                 SendToProgram("go\n", cps->other);
6598             }
6599             cps->other->maybeThinking = TRUE;
6600         }
6601
6602         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6603         
6604         if (!pausing && appData.ringBellAfterMoves) {
6605             RingBell();
6606         }
6607
6608         /* 
6609          * Reenable menu items that were disabled while
6610          * machine was thinking
6611          */
6612         if (gameMode != TwoMachinesPlay)
6613             SetUserThinkingEnables();
6614
6615         // [HGM] book: after book hit opponent has received move and is now in force mode
6616         // force the book reply into it, and then fake that it outputted this move by jumping
6617         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6618         if(bookHit) {
6619                 static char bookMove[MSG_SIZ]; // a bit generous?
6620
6621                 strcpy(bookMove, "move ");
6622                 strcat(bookMove, bookHit);
6623                 message = bookMove;
6624                 cps = cps->other;
6625                 programStats.nodes = programStats.depth = programStats.time = 
6626                 programStats.score = programStats.got_only_move = 0;
6627                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6628
6629                 if(cps->lastPing != cps->lastPong) {
6630                     savedMessage = message; // args for deferred call
6631                     savedState = cps;
6632                     ScheduleDelayedEvent(DeferredBookMove, 10);
6633                     return;
6634                 }
6635                 goto FakeBookMove;
6636         }
6637
6638         return;
6639     }
6640
6641     /* Set special modes for chess engines.  Later something general
6642      *  could be added here; for now there is just one kludge feature,
6643      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6644      *  when "xboard" is given as an interactive command.
6645      */
6646     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6647         cps->useSigint = FALSE;
6648         cps->useSigterm = FALSE;
6649     }
6650     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6651       ParseFeatures(message+8, cps);
6652       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6653     }
6654
6655     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6656      * want this, I was asked to put it in, and obliged.
6657      */
6658     if (!strncmp(message, "setboard ", 9)) {
6659         Board initial_position;
6660
6661         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6662
6663         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6664             DisplayError(_("Bad FEN received from engine"), 0);
6665             return ;
6666         } else {
6667            Reset(TRUE, FALSE);
6668            CopyBoard(boards[0], initial_position);
6669            initialRulePlies = FENrulePlies;
6670            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6671            else gameMode = MachinePlaysBlack;                 
6672            DrawPosition(FALSE, boards[currentMove]);
6673         }
6674         return;
6675     }
6676
6677     /*
6678      * Look for communication commands
6679      */
6680     if (!strncmp(message, "telluser ", 9)) {
6681         DisplayNote(message + 9);
6682         return;
6683     }
6684     if (!strncmp(message, "tellusererror ", 14)) {
6685         cps->userError = 1;
6686         DisplayError(message + 14, 0);
6687         return;
6688     }
6689     if (!strncmp(message, "tellopponent ", 13)) {
6690       if (appData.icsActive) {
6691         if (loggedOn) {
6692           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6693           SendToICS(buf1);
6694         }
6695       } else {
6696         DisplayNote(message + 13);
6697       }
6698       return;
6699     }
6700     if (!strncmp(message, "tellothers ", 11)) {
6701       if (appData.icsActive) {
6702         if (loggedOn) {
6703           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6704           SendToICS(buf1);
6705         }
6706       }
6707       return;
6708     }
6709     if (!strncmp(message, "tellall ", 8)) {
6710       if (appData.icsActive) {
6711         if (loggedOn) {
6712           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6713           SendToICS(buf1);
6714         }
6715       } else {
6716         DisplayNote(message + 8);
6717       }
6718       return;
6719     }
6720     if (strncmp(message, "warning", 7) == 0) {
6721         /* Undocumented feature, use tellusererror in new code */
6722         DisplayError(message, 0);
6723         return;
6724     }
6725     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6726         strcpy(realname, cps->tidy);
6727         strcat(realname, " query");
6728         AskQuestion(realname, buf2, buf1, cps->pr);
6729         return;
6730     }
6731     /* Commands from the engine directly to ICS.  We don't allow these to be 
6732      *  sent until we are logged on. Crafty kibitzes have been known to 
6733      *  interfere with the login process.
6734      */
6735     if (loggedOn) {
6736         if (!strncmp(message, "tellics ", 8)) {
6737             SendToICS(message + 8);
6738             SendToICS("\n");
6739             return;
6740         }
6741         if (!strncmp(message, "tellicsnoalias ", 15)) {
6742             SendToICS(ics_prefix);
6743             SendToICS(message + 15);
6744             SendToICS("\n");
6745             return;
6746         }
6747         /* The following are for backward compatibility only */
6748         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6749             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6750             SendToICS(ics_prefix);
6751             SendToICS(message);
6752             SendToICS("\n");
6753             return;
6754         }
6755     }
6756     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6757         return;
6758     }
6759     /*
6760      * If the move is illegal, cancel it and redraw the board.
6761      * Also deal with other error cases.  Matching is rather loose
6762      * here to accommodate engines written before the spec.
6763      */
6764     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6765         strncmp(message, "Error", 5) == 0) {
6766         if (StrStr(message, "name") || 
6767             StrStr(message, "rating") || StrStr(message, "?") ||
6768             StrStr(message, "result") || StrStr(message, "board") ||
6769             StrStr(message, "bk") || StrStr(message, "computer") ||
6770             StrStr(message, "variant") || StrStr(message, "hint") ||
6771             StrStr(message, "random") || StrStr(message, "depth") ||
6772             StrStr(message, "accepted")) {
6773             return;
6774         }
6775         if (StrStr(message, "protover")) {
6776           /* Program is responding to input, so it's apparently done
6777              initializing, and this error message indicates it is
6778              protocol version 1.  So we don't need to wait any longer
6779              for it to initialize and send feature commands. */
6780           FeatureDone(cps, 1);
6781           cps->protocolVersion = 1;
6782           return;
6783         }
6784         cps->maybeThinking = FALSE;
6785
6786         if (StrStr(message, "draw")) {
6787             /* Program doesn't have "draw" command */
6788             cps->sendDrawOffers = 0;
6789             return;
6790         }
6791         if (cps->sendTime != 1 &&
6792             (StrStr(message, "time") || StrStr(message, "otim"))) {
6793           /* Program apparently doesn't have "time" or "otim" command */
6794           cps->sendTime = 0;
6795           return;
6796         }
6797         if (StrStr(message, "analyze")) {
6798             cps->analysisSupport = FALSE;
6799             cps->analyzing = FALSE;
6800             Reset(FALSE, TRUE);
6801             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6802             DisplayError(buf2, 0);
6803             return;
6804         }
6805         if (StrStr(message, "(no matching move)st")) {
6806           /* Special kludge for GNU Chess 4 only */
6807           cps->stKludge = TRUE;
6808           SendTimeControl(cps, movesPerSession, timeControl,
6809                           timeIncrement, appData.searchDepth,
6810                           searchTime);
6811           return;
6812         }
6813         if (StrStr(message, "(no matching move)sd")) {
6814           /* Special kludge for GNU Chess 4 only */
6815           cps->sdKludge = TRUE;
6816           SendTimeControl(cps, movesPerSession, timeControl,
6817                           timeIncrement, appData.searchDepth,
6818                           searchTime);
6819           return;
6820         }
6821         if (!StrStr(message, "llegal")) {
6822             return;
6823         }
6824         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6825             gameMode == IcsIdle) return;
6826         if (forwardMostMove <= backwardMostMove) return;
6827         if (pausing) PauseEvent();
6828       if(appData.forceIllegal) {
6829             // [HGM] illegal: machine refused move; force position after move into it
6830           SendToProgram("force\n", cps);
6831           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6832                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6833                 // when black is to move, while there might be nothing on a2 or black
6834                 // might already have the move. So send the board as if white has the move.
6835                 // But first we must change the stm of the engine, as it refused the last move
6836                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6837                 if(WhiteOnMove(forwardMostMove)) {
6838                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6839                     SendBoard(cps, forwardMostMove); // kludgeless board
6840                 } else {
6841                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6842                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6843                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6844                 }
6845           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6846             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6847                  gameMode == TwoMachinesPlay)
6848               SendToProgram("go\n", cps);
6849             return;
6850       } else
6851         if (gameMode == PlayFromGameFile) {
6852             /* Stop reading this game file */
6853             gameMode = EditGame;
6854             ModeHighlight();
6855         }
6856         currentMove = --forwardMostMove;
6857         DisplayMove(currentMove-1); /* before DisplayMoveError */
6858         SwitchClocks();
6859         DisplayBothClocks();
6860         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6861                 parseList[currentMove], cps->which);
6862         DisplayMoveError(buf1);
6863         DrawPosition(FALSE, boards[currentMove]);
6864
6865         /* [HGM] illegal-move claim should forfeit game when Xboard */
6866         /* only passes fully legal moves                            */
6867         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6868             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6869                                 "False illegal-move claim", GE_XBOARD );
6870         }
6871         return;
6872     }
6873     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6874         /* Program has a broken "time" command that
6875            outputs a string not ending in newline.
6876            Don't use it. */
6877         cps->sendTime = 0;
6878     }
6879     
6880     /*
6881      * If chess program startup fails, exit with an error message.
6882      * Attempts to recover here are futile.
6883      */
6884     if ((StrStr(message, "unknown host") != NULL)
6885         || (StrStr(message, "No remote directory") != NULL)
6886         || (StrStr(message, "not found") != NULL)
6887         || (StrStr(message, "No such file") != NULL)
6888         || (StrStr(message, "can't alloc") != NULL)
6889         || (StrStr(message, "Permission denied") != NULL)) {
6890
6891         cps->maybeThinking = FALSE;
6892         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6893                 cps->which, cps->program, cps->host, message);
6894         RemoveInputSource(cps->isr);
6895         DisplayFatalError(buf1, 0, 1);
6896         return;
6897     }
6898     
6899     /* 
6900      * Look for hint output
6901      */
6902     if (sscanf(message, "Hint: %s", buf1) == 1) {
6903         if (cps == &first && hintRequested) {
6904             hintRequested = FALSE;
6905             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6906                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6907                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6908                                     PosFlags(forwardMostMove),
6909                                     fromY, fromX, toY, toX, promoChar, buf1);
6910                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6911                 DisplayInformation(buf2);
6912             } else {
6913                 /* Hint move could not be parsed!? */
6914               snprintf(buf2, sizeof(buf2),
6915                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6916                         buf1, cps->which);
6917                 DisplayError(buf2, 0);
6918             }
6919         } else {
6920             strcpy(lastHint, buf1);
6921         }
6922         return;
6923     }
6924
6925     /*
6926      * Ignore other messages if game is not in progress
6927      */
6928     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6929         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6930
6931     /*
6932      * look for win, lose, draw, or draw offer
6933      */
6934     if (strncmp(message, "1-0", 3) == 0) {
6935         char *p, *q, *r = "";
6936         p = strchr(message, '{');
6937         if (p) {
6938             q = strchr(p, '}');
6939             if (q) {
6940                 *q = NULLCHAR;
6941                 r = p + 1;
6942             }
6943         }
6944         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6945         return;
6946     } else if (strncmp(message, "0-1", 3) == 0) {
6947         char *p, *q, *r = "";
6948         p = strchr(message, '{');
6949         if (p) {
6950             q = strchr(p, '}');
6951             if (q) {
6952                 *q = NULLCHAR;
6953                 r = p + 1;
6954             }
6955         }
6956         /* Kludge for Arasan 4.1 bug */
6957         if (strcmp(r, "Black resigns") == 0) {
6958             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6959             return;
6960         }
6961         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6962         return;
6963     } else if (strncmp(message, "1/2", 3) == 0) {
6964         char *p, *q, *r = "";
6965         p = strchr(message, '{');
6966         if (p) {
6967             q = strchr(p, '}');
6968             if (q) {
6969                 *q = NULLCHAR;
6970                 r = p + 1;
6971             }
6972         }
6973             
6974         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6975         return;
6976
6977     } else if (strncmp(message, "White resign", 12) == 0) {
6978         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6979         return;
6980     } else if (strncmp(message, "Black resign", 12) == 0) {
6981         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6982         return;
6983     } else if (strncmp(message, "White matches", 13) == 0 ||
6984                strncmp(message, "Black matches", 13) == 0   ) {
6985         /* [HGM] ignore GNUShogi noises */
6986         return;
6987     } else if (strncmp(message, "White", 5) == 0 &&
6988                message[5] != '(' &&
6989                StrStr(message, "Black") == NULL) {
6990         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6991         return;
6992     } else if (strncmp(message, "Black", 5) == 0 &&
6993                message[5] != '(') {
6994         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6995         return;
6996     } else if (strcmp(message, "resign") == 0 ||
6997                strcmp(message, "computer resigns") == 0) {
6998         switch (gameMode) {
6999           case MachinePlaysBlack:
7000           case IcsPlayingBlack:
7001             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7002             break;
7003           case MachinePlaysWhite:
7004           case IcsPlayingWhite:
7005             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7006             break;
7007           case TwoMachinesPlay:
7008             if (cps->twoMachinesColor[0] == 'w')
7009               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7010             else
7011               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7012             break;
7013           default:
7014             /* can't happen */
7015             break;
7016         }
7017         return;
7018     } else if (strncmp(message, "opponent mates", 14) == 0) {
7019         switch (gameMode) {
7020           case MachinePlaysBlack:
7021           case IcsPlayingBlack:
7022             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7023             break;
7024           case MachinePlaysWhite:
7025           case IcsPlayingWhite:
7026             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7027             break;
7028           case TwoMachinesPlay:
7029             if (cps->twoMachinesColor[0] == 'w')
7030               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7031             else
7032               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7033             break;
7034           default:
7035             /* can't happen */
7036             break;
7037         }
7038         return;
7039     } else if (strncmp(message, "computer mates", 14) == 0) {
7040         switch (gameMode) {
7041           case MachinePlaysBlack:
7042           case IcsPlayingBlack:
7043             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7044             break;
7045           case MachinePlaysWhite:
7046           case IcsPlayingWhite:
7047             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7048             break;
7049           case TwoMachinesPlay:
7050             if (cps->twoMachinesColor[0] == 'w')
7051               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7052             else
7053               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7054             break;
7055           default:
7056             /* can't happen */
7057             break;
7058         }
7059         return;
7060     } else if (strncmp(message, "checkmate", 9) == 0) {
7061         if (WhiteOnMove(forwardMostMove)) {
7062             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7063         } else {
7064             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7065         }
7066         return;
7067     } else if (strstr(message, "Draw") != NULL ||
7068                strstr(message, "game is a draw") != NULL) {
7069         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7070         return;
7071     } else if (strstr(message, "offer") != NULL &&
7072                strstr(message, "draw") != NULL) {
7073 #if ZIPPY
7074         if (appData.zippyPlay && first.initDone) {
7075             /* Relay offer to ICS */
7076             SendToICS(ics_prefix);
7077             SendToICS("draw\n");
7078         }
7079 #endif
7080         cps->offeredDraw = 2; /* valid until this engine moves twice */
7081         if (gameMode == TwoMachinesPlay) {
7082             if (cps->other->offeredDraw) {
7083                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7084             /* [HGM] in two-machine mode we delay relaying draw offer      */
7085             /* until after we also have move, to see if it is really claim */
7086             }
7087         } else if (gameMode == MachinePlaysWhite ||
7088                    gameMode == MachinePlaysBlack) {
7089           if (userOfferedDraw) {
7090             DisplayInformation(_("Machine accepts your draw offer"));
7091             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7092           } else {
7093             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7094           }
7095         }
7096     }
7097
7098     
7099     /*
7100      * Look for thinking output
7101      */
7102     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7103           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7104                                 ) {
7105         int plylev, mvleft, mvtot, curscore, time;
7106         char mvname[MOVE_LEN];
7107         u64 nodes; // [DM]
7108         char plyext;
7109         int ignore = FALSE;
7110         int prefixHint = FALSE;
7111         mvname[0] = NULLCHAR;
7112
7113         switch (gameMode) {
7114           case MachinePlaysBlack:
7115           case IcsPlayingBlack:
7116             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7117             break;
7118           case MachinePlaysWhite:
7119           case IcsPlayingWhite:
7120             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7121             break;
7122           case AnalyzeMode:
7123           case AnalyzeFile:
7124             break;
7125           case IcsObserving: /* [DM] icsEngineAnalyze */
7126             if (!appData.icsEngineAnalyze) ignore = TRUE;
7127             break;
7128           case TwoMachinesPlay:
7129             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7130                 ignore = TRUE;
7131             }
7132             break;
7133           default:
7134             ignore = TRUE;
7135             break;
7136         }
7137
7138         if (!ignore) {
7139             buf1[0] = NULLCHAR;
7140             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7141                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7142
7143                 if (plyext != ' ' && plyext != '\t') {
7144                     time *= 100;
7145                 }
7146
7147                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7148                 if( cps->scoreIsAbsolute && 
7149                     ( gameMode == MachinePlaysBlack ||
7150                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7151                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7152                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7153                      !WhiteOnMove(currentMove)
7154                     ) )
7155                 {
7156                     curscore = -curscore;
7157                 }
7158
7159
7160                 programStats.depth = plylev;
7161                 programStats.nodes = nodes;
7162                 programStats.time = time;
7163                 programStats.score = curscore;
7164                 programStats.got_only_move = 0;
7165
7166                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7167                         int ticklen;
7168
7169                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7170                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7171                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7172                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7173                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7174                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7175                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7176                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7177                 }
7178
7179                 /* Buffer overflow protection */
7180                 if (buf1[0] != NULLCHAR) {
7181                     if (strlen(buf1) >= sizeof(programStats.movelist)
7182                         && appData.debugMode) {
7183                         fprintf(debugFP,
7184                                 "PV is too long; using the first %u bytes.\n",
7185                                 (unsigned) sizeof(programStats.movelist) - 1);
7186                     }
7187
7188                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7189                 } else {
7190                     sprintf(programStats.movelist, " no PV\n");
7191                 }
7192
7193                 if (programStats.seen_stat) {
7194                     programStats.ok_to_send = 1;
7195                 }
7196
7197                 if (strchr(programStats.movelist, '(') != NULL) {
7198                     programStats.line_is_book = 1;
7199                     programStats.nr_moves = 0;
7200                     programStats.moves_left = 0;
7201                 } else {
7202                     programStats.line_is_book = 0;
7203                 }
7204
7205                 SendProgramStatsToFrontend( cps, &programStats );
7206
7207                 /* 
7208                     [AS] Protect the thinkOutput buffer from overflow... this
7209                     is only useful if buf1 hasn't overflowed first!
7210                 */
7211                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7212                         plylev, 
7213                         (gameMode == TwoMachinesPlay ?
7214                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7215                         ((double) curscore) / 100.0,
7216                         prefixHint ? lastHint : "",
7217                         prefixHint ? " " : "" );
7218
7219                 if( buf1[0] != NULLCHAR ) {
7220                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7221
7222                     if( strlen(buf1) > max_len ) {
7223                         if( appData.debugMode) {
7224                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7225                         }
7226                         buf1[max_len+1] = '\0';
7227                     }
7228
7229                     strcat( thinkOutput, buf1 );
7230                 }
7231
7232                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7233                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7234                     DisplayMove(currentMove - 1);
7235                 }
7236                 return;
7237
7238             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7239                 /* crafty (9.25+) says "(only move) <move>"
7240                  * if there is only 1 legal move
7241                  */
7242                 sscanf(p, "(only move) %s", buf1);
7243                 sprintf(thinkOutput, "%s (only move)", buf1);
7244                 sprintf(programStats.movelist, "%s (only move)", buf1);
7245                 programStats.depth = 1;
7246                 programStats.nr_moves = 1;
7247                 programStats.moves_left = 1;
7248                 programStats.nodes = 1;
7249                 programStats.time = 1;
7250                 programStats.got_only_move = 1;
7251
7252                 /* Not really, but we also use this member to
7253                    mean "line isn't going to change" (Crafty
7254                    isn't searching, so stats won't change) */
7255                 programStats.line_is_book = 1;
7256
7257                 SendProgramStatsToFrontend( cps, &programStats );
7258                 
7259                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7260                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7261                     DisplayMove(currentMove - 1);
7262                 }
7263                 return;
7264             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7265                               &time, &nodes, &plylev, &mvleft,
7266                               &mvtot, mvname) >= 5) {
7267                 /* The stat01: line is from Crafty (9.29+) in response
7268                    to the "." command */
7269                 programStats.seen_stat = 1;
7270                 cps->maybeThinking = TRUE;
7271
7272                 if (programStats.got_only_move || !appData.periodicUpdates)
7273                   return;
7274
7275                 programStats.depth = plylev;
7276                 programStats.time = time;
7277                 programStats.nodes = nodes;
7278                 programStats.moves_left = mvleft;
7279                 programStats.nr_moves = mvtot;
7280                 strcpy(programStats.move_name, mvname);
7281                 programStats.ok_to_send = 1;
7282                 programStats.movelist[0] = '\0';
7283
7284                 SendProgramStatsToFrontend( cps, &programStats );
7285
7286                 return;
7287
7288             } else if (strncmp(message,"++",2) == 0) {
7289                 /* Crafty 9.29+ outputs this */
7290                 programStats.got_fail = 2;
7291                 return;
7292
7293             } else if (strncmp(message,"--",2) == 0) {
7294                 /* Crafty 9.29+ outputs this */
7295                 programStats.got_fail = 1;
7296                 return;
7297
7298             } else if (thinkOutput[0] != NULLCHAR &&
7299                        strncmp(message, "    ", 4) == 0) {
7300                 unsigned message_len;
7301
7302                 p = message;
7303                 while (*p && *p == ' ') p++;
7304
7305                 message_len = strlen( p );
7306
7307                 /* [AS] Avoid buffer overflow */
7308                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7309                     strcat(thinkOutput, " ");
7310                     strcat(thinkOutput, p);
7311                 }
7312
7313                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7314                     strcat(programStats.movelist, " ");
7315                     strcat(programStats.movelist, p);
7316                 }
7317
7318                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7319                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7320                     DisplayMove(currentMove - 1);
7321                 }
7322                 return;
7323             }
7324         }
7325         else {
7326             buf1[0] = NULLCHAR;
7327
7328             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7329                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7330             {
7331                 ChessProgramStats cpstats;
7332
7333                 if (plyext != ' ' && plyext != '\t') {
7334                     time *= 100;
7335                 }
7336
7337                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7338                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7339                     curscore = -curscore;
7340                 }
7341
7342                 cpstats.depth = plylev;
7343                 cpstats.nodes = nodes;
7344                 cpstats.time = time;
7345                 cpstats.score = curscore;
7346                 cpstats.got_only_move = 0;
7347                 cpstats.movelist[0] = '\0';
7348
7349                 if (buf1[0] != NULLCHAR) {
7350                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7351                 }
7352
7353                 cpstats.ok_to_send = 0;
7354                 cpstats.line_is_book = 0;
7355                 cpstats.nr_moves = 0;
7356                 cpstats.moves_left = 0;
7357
7358                 SendProgramStatsToFrontend( cps, &cpstats );
7359             }
7360         }
7361     }
7362 }
7363
7364
7365 /* Parse a game score from the character string "game", and
7366    record it as the history of the current game.  The game
7367    score is NOT assumed to start from the standard position. 
7368    The display is not updated in any way.
7369    */
7370 void
7371 ParseGameHistory(game)
7372      char *game;
7373 {
7374     ChessMove moveType;
7375     int fromX, fromY, toX, toY, boardIndex;
7376     char promoChar;
7377     char *p, *q;
7378     char buf[MSG_SIZ];
7379
7380     if (appData.debugMode)
7381       fprintf(debugFP, "Parsing game history: %s\n", game);
7382
7383     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7384     gameInfo.site = StrSave(appData.icsHost);
7385     gameInfo.date = PGNDate();
7386     gameInfo.round = StrSave("-");
7387
7388     /* Parse out names of players */
7389     while (*game == ' ') game++;
7390     p = buf;
7391     while (*game != ' ') *p++ = *game++;
7392     *p = NULLCHAR;
7393     gameInfo.white = StrSave(buf);
7394     while (*game == ' ') game++;
7395     p = buf;
7396     while (*game != ' ' && *game != '\n') *p++ = *game++;
7397     *p = NULLCHAR;
7398     gameInfo.black = StrSave(buf);
7399
7400     /* Parse moves */
7401     boardIndex = blackPlaysFirst ? 1 : 0;
7402     yynewstr(game);
7403     for (;;) {
7404         yyboardindex = boardIndex;
7405         moveType = (ChessMove) yylex();
7406         switch (moveType) {
7407           case IllegalMove:             /* maybe suicide chess, etc. */
7408   if (appData.debugMode) {
7409     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7410     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7411     setbuf(debugFP, NULL);
7412   }
7413           case WhitePromotionChancellor:
7414           case BlackPromotionChancellor:
7415           case WhitePromotionArchbishop:
7416           case BlackPromotionArchbishop:
7417           case WhitePromotionQueen:
7418           case BlackPromotionQueen:
7419           case WhitePromotionRook:
7420           case BlackPromotionRook:
7421           case WhitePromotionBishop:
7422           case BlackPromotionBishop:
7423           case WhitePromotionKnight:
7424           case BlackPromotionKnight:
7425           case WhitePromotionKing:
7426           case BlackPromotionKing:
7427           case NormalMove:
7428           case WhiteCapturesEnPassant:
7429           case BlackCapturesEnPassant:
7430           case WhiteKingSideCastle:
7431           case WhiteQueenSideCastle:
7432           case BlackKingSideCastle:
7433           case BlackQueenSideCastle:
7434           case WhiteKingSideCastleWild:
7435           case WhiteQueenSideCastleWild:
7436           case BlackKingSideCastleWild:
7437           case BlackQueenSideCastleWild:
7438           /* PUSH Fabien */
7439           case WhiteHSideCastleFR:
7440           case WhiteASideCastleFR:
7441           case BlackHSideCastleFR:
7442           case BlackASideCastleFR:
7443           /* POP Fabien */
7444             fromX = currentMoveString[0] - AAA;
7445             fromY = currentMoveString[1] - ONE;
7446             toX = currentMoveString[2] - AAA;
7447             toY = currentMoveString[3] - ONE;
7448             promoChar = currentMoveString[4];
7449             break;
7450           case WhiteDrop:
7451           case BlackDrop:
7452             fromX = moveType == WhiteDrop ?
7453               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7454             (int) CharToPiece(ToLower(currentMoveString[0]));
7455             fromY = DROP_RANK;
7456             toX = currentMoveString[2] - AAA;
7457             toY = currentMoveString[3] - ONE;
7458             promoChar = NULLCHAR;
7459             break;
7460           case AmbiguousMove:
7461             /* bug? */
7462             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7463   if (appData.debugMode) {
7464     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7465     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7466     setbuf(debugFP, NULL);
7467   }
7468             DisplayError(buf, 0);
7469             return;
7470           case ImpossibleMove:
7471             /* bug? */
7472             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7473   if (appData.debugMode) {
7474     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7475     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7476     setbuf(debugFP, NULL);
7477   }
7478             DisplayError(buf, 0);
7479             return;
7480           case (ChessMove) 0:   /* end of file */
7481             if (boardIndex < backwardMostMove) {
7482                 /* Oops, gap.  How did that happen? */
7483                 DisplayError(_("Gap in move list"), 0);
7484                 return;
7485             }
7486             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7487             if (boardIndex > forwardMostMove) {
7488                 forwardMostMove = boardIndex;
7489             }
7490             return;
7491           case ElapsedTime:
7492             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7493                 strcat(parseList[boardIndex-1], " ");
7494                 strcat(parseList[boardIndex-1], yy_text);
7495             }
7496             continue;
7497           case Comment:
7498           case PGNTag:
7499           case NAG:
7500           default:
7501             /* ignore */
7502             continue;
7503           case WhiteWins:
7504           case BlackWins:
7505           case GameIsDrawn:
7506           case GameUnfinished:
7507             if (gameMode == IcsExamining) {
7508                 if (boardIndex < backwardMostMove) {
7509                     /* Oops, gap.  How did that happen? */
7510                     return;
7511                 }
7512                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7513                 return;
7514             }
7515             gameInfo.result = moveType;
7516             p = strchr(yy_text, '{');
7517             if (p == NULL) p = strchr(yy_text, '(');
7518             if (p == NULL) {
7519                 p = yy_text;
7520                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7521             } else {
7522                 q = strchr(p, *p == '{' ? '}' : ')');
7523                 if (q != NULL) *q = NULLCHAR;
7524                 p++;
7525             }
7526             gameInfo.resultDetails = StrSave(p);
7527             continue;
7528         }
7529         if (boardIndex >= forwardMostMove &&
7530             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7531             backwardMostMove = blackPlaysFirst ? 1 : 0;
7532             return;
7533         }
7534         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7535                                  fromY, fromX, toY, toX, promoChar,
7536                                  parseList[boardIndex]);
7537         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7538         /* currentMoveString is set as a side-effect of yylex */
7539         strcpy(moveList[boardIndex], currentMoveString);
7540         strcat(moveList[boardIndex], "\n");
7541         boardIndex++;
7542         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7543         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7544           case MT_NONE:
7545           case MT_STALEMATE:
7546           default:
7547             break;
7548           case MT_CHECK:
7549             if(gameInfo.variant != VariantShogi)
7550                 strcat(parseList[boardIndex - 1], "+");
7551             break;
7552           case MT_CHECKMATE:
7553           case MT_STAINMATE:
7554             strcat(parseList[boardIndex - 1], "#");
7555             break;
7556         }
7557     }
7558 }
7559
7560
7561 /* Apply a move to the given board  */
7562 void
7563 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7564      int fromX, fromY, toX, toY;
7565      int promoChar;
7566      Board board;
7567 {
7568   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7569   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7570
7571     /* [HGM] compute & store e.p. status and castling rights for new position */
7572     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7573     { int i;
7574
7575       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7576       oldEP = (signed char)board[EP_STATUS];
7577       board[EP_STATUS] = EP_NONE;
7578
7579       if( board[toY][toX] != EmptySquare ) 
7580            board[EP_STATUS] = EP_CAPTURE;  
7581
7582       if( board[fromY][fromX] == WhitePawn ) {
7583            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7584                board[EP_STATUS] = EP_PAWN_MOVE;
7585            if( toY-fromY==2) {
7586                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7587                         gameInfo.variant != VariantBerolina || toX < fromX)
7588                       board[EP_STATUS] = toX | berolina;
7589                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7590                         gameInfo.variant != VariantBerolina || toX > fromX) 
7591                       board[EP_STATUS] = toX;
7592            }
7593       } else 
7594       if( board[fromY][fromX] == BlackPawn ) {
7595            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7596                board[EP_STATUS] = EP_PAWN_MOVE; 
7597            if( toY-fromY== -2) {
7598                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7599                         gameInfo.variant != VariantBerolina || toX < fromX)
7600                       board[EP_STATUS] = toX | berolina;
7601                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7602                         gameInfo.variant != VariantBerolina || toX > fromX) 
7603                       board[EP_STATUS] = toX;
7604            }
7605        }
7606
7607        for(i=0; i<nrCastlingRights; i++) {
7608            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7609               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7610              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7611        }
7612
7613     }
7614
7615   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7616   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7617        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7618          
7619   if (fromX == toX && fromY == toY) return;
7620
7621   if (fromY == DROP_RANK) {
7622         /* must be first */
7623         piece = board[toY][toX] = (ChessSquare) fromX;
7624   } else {
7625      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7626      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7627      if(gameInfo.variant == VariantKnightmate)
7628          king += (int) WhiteUnicorn - (int) WhiteKing;
7629
7630     /* Code added by Tord: */
7631     /* FRC castling assumed when king captures friendly rook. */
7632     if (board[fromY][fromX] == WhiteKing &&
7633              board[toY][toX] == WhiteRook) {
7634       board[fromY][fromX] = EmptySquare;
7635       board[toY][toX] = EmptySquare;
7636       if(toX > fromX) {
7637         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7638       } else {
7639         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7640       }
7641     } else if (board[fromY][fromX] == BlackKing &&
7642                board[toY][toX] == BlackRook) {
7643       board[fromY][fromX] = EmptySquare;
7644       board[toY][toX] = EmptySquare;
7645       if(toX > fromX) {
7646         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7647       } else {
7648         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7649       }
7650     /* End of code added by Tord */
7651
7652     } else if (board[fromY][fromX] == king
7653         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7654         && toY == fromY && toX > fromX+1) {
7655         board[fromY][fromX] = EmptySquare;
7656         board[toY][toX] = king;
7657         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7658         board[fromY][BOARD_RGHT-1] = EmptySquare;
7659     } else if (board[fromY][fromX] == king
7660         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7661                && toY == fromY && toX < fromX-1) {
7662         board[fromY][fromX] = EmptySquare;
7663         board[toY][toX] = king;
7664         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7665         board[fromY][BOARD_LEFT] = EmptySquare;
7666     } else if (board[fromY][fromX] == WhitePawn
7667                && toY >= BOARD_HEIGHT-promoRank
7668                && gameInfo.variant != VariantXiangqi
7669                ) {
7670         /* white pawn promotion */
7671         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7672         if (board[toY][toX] == EmptySquare) {
7673             board[toY][toX] = WhiteQueen;
7674         }
7675         if(gameInfo.variant==VariantBughouse ||
7676            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7677             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7678         board[fromY][fromX] = EmptySquare;
7679     } else if ((fromY == BOARD_HEIGHT-4)
7680                && (toX != fromX)
7681                && gameInfo.variant != VariantXiangqi
7682                && gameInfo.variant != VariantBerolina
7683                && (board[fromY][fromX] == WhitePawn)
7684                && (board[toY][toX] == EmptySquare)) {
7685         board[fromY][fromX] = EmptySquare;
7686         board[toY][toX] = WhitePawn;
7687         captured = board[toY - 1][toX];
7688         board[toY - 1][toX] = EmptySquare;
7689     } else if ((fromY == BOARD_HEIGHT-4)
7690                && (toX == fromX)
7691                && gameInfo.variant == VariantBerolina
7692                && (board[fromY][fromX] == WhitePawn)
7693                && (board[toY][toX] == EmptySquare)) {
7694         board[fromY][fromX] = EmptySquare;
7695         board[toY][toX] = WhitePawn;
7696         if(oldEP & EP_BEROLIN_A) {
7697                 captured = board[fromY][fromX-1];
7698                 board[fromY][fromX-1] = EmptySquare;
7699         }else{  captured = board[fromY][fromX+1];
7700                 board[fromY][fromX+1] = EmptySquare;
7701         }
7702     } else if (board[fromY][fromX] == king
7703         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7704                && toY == fromY && toX > fromX+1) {
7705         board[fromY][fromX] = EmptySquare;
7706         board[toY][toX] = king;
7707         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7708         board[fromY][BOARD_RGHT-1] = EmptySquare;
7709     } else if (board[fromY][fromX] == king
7710         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7711                && toY == fromY && toX < fromX-1) {
7712         board[fromY][fromX] = EmptySquare;
7713         board[toY][toX] = king;
7714         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7715         board[fromY][BOARD_LEFT] = EmptySquare;
7716     } else if (fromY == 7 && fromX == 3
7717                && board[fromY][fromX] == BlackKing
7718                && toY == 7 && toX == 5) {
7719         board[fromY][fromX] = EmptySquare;
7720         board[toY][toX] = BlackKing;
7721         board[fromY][7] = EmptySquare;
7722         board[toY][4] = BlackRook;
7723     } else if (fromY == 7 && fromX == 3
7724                && board[fromY][fromX] == BlackKing
7725                && toY == 7 && toX == 1) {
7726         board[fromY][fromX] = EmptySquare;
7727         board[toY][toX] = BlackKing;
7728         board[fromY][0] = EmptySquare;
7729         board[toY][2] = BlackRook;
7730     } else if (board[fromY][fromX] == BlackPawn
7731                && toY < promoRank
7732                && gameInfo.variant != VariantXiangqi
7733                ) {
7734         /* black pawn promotion */
7735         board[toY][toX] = CharToPiece(ToLower(promoChar));
7736         if (board[toY][toX] == EmptySquare) {
7737             board[toY][toX] = BlackQueen;
7738         }
7739         if(gameInfo.variant==VariantBughouse ||
7740            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7741             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7742         board[fromY][fromX] = EmptySquare;
7743     } else if ((fromY == 3)
7744                && (toX != fromX)
7745                && gameInfo.variant != VariantXiangqi
7746                && gameInfo.variant != VariantBerolina
7747                && (board[fromY][fromX] == BlackPawn)
7748                && (board[toY][toX] == EmptySquare)) {
7749         board[fromY][fromX] = EmptySquare;
7750         board[toY][toX] = BlackPawn;
7751         captured = board[toY + 1][toX];
7752         board[toY + 1][toX] = EmptySquare;
7753     } else if ((fromY == 3)
7754                && (toX == fromX)
7755                && gameInfo.variant == VariantBerolina
7756                && (board[fromY][fromX] == BlackPawn)
7757                && (board[toY][toX] == EmptySquare)) {
7758         board[fromY][fromX] = EmptySquare;
7759         board[toY][toX] = BlackPawn;
7760         if(oldEP & EP_BEROLIN_A) {
7761                 captured = board[fromY][fromX-1];
7762                 board[fromY][fromX-1] = EmptySquare;
7763         }else{  captured = board[fromY][fromX+1];
7764                 board[fromY][fromX+1] = EmptySquare;
7765         }
7766     } else {
7767         board[toY][toX] = board[fromY][fromX];
7768         board[fromY][fromX] = EmptySquare;
7769     }
7770
7771     /* [HGM] now we promote for Shogi, if needed */
7772     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7773         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7774   }
7775
7776     if (gameInfo.holdingsWidth != 0) {
7777
7778       /* !!A lot more code needs to be written to support holdings  */
7779       /* [HGM] OK, so I have written it. Holdings are stored in the */
7780       /* penultimate board files, so they are automaticlly stored   */
7781       /* in the game history.                                       */
7782       if (fromY == DROP_RANK) {
7783         /* Delete from holdings, by decreasing count */
7784         /* and erasing image if necessary            */
7785         p = (int) fromX;
7786         if(p < (int) BlackPawn) { /* white drop */
7787              p -= (int)WhitePawn;
7788                  p = PieceToNumber((ChessSquare)p);
7789              if(p >= gameInfo.holdingsSize) p = 0;
7790              if(--board[p][BOARD_WIDTH-2] <= 0)
7791                   board[p][BOARD_WIDTH-1] = EmptySquare;
7792              if((int)board[p][BOARD_WIDTH-2] < 0)
7793                         board[p][BOARD_WIDTH-2] = 0;
7794         } else {                  /* black drop */
7795              p -= (int)BlackPawn;
7796                  p = PieceToNumber((ChessSquare)p);
7797              if(p >= gameInfo.holdingsSize) p = 0;
7798              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7799                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7800              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7801                         board[BOARD_HEIGHT-1-p][1] = 0;
7802         }
7803       }
7804       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7805           && gameInfo.variant != VariantBughouse        ) {
7806         /* [HGM] holdings: Add to holdings, if holdings exist */
7807         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7808                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7809                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7810         }
7811         p = (int) captured;
7812         if (p >= (int) BlackPawn) {
7813           p -= (int)BlackPawn;
7814           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7815                   /* in Shogi restore piece to its original  first */
7816                   captured = (ChessSquare) (DEMOTED captured);
7817                   p = DEMOTED p;
7818           }
7819           p = PieceToNumber((ChessSquare)p);
7820           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7821           board[p][BOARD_WIDTH-2]++;
7822           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7823         } else {
7824           p -= (int)WhitePawn;
7825           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7826                   captured = (ChessSquare) (DEMOTED captured);
7827                   p = DEMOTED p;
7828           }
7829           p = PieceToNumber((ChessSquare)p);
7830           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7831           board[BOARD_HEIGHT-1-p][1]++;
7832           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7833         }
7834       }
7835     } else if (gameInfo.variant == VariantAtomic) {
7836       if (captured != EmptySquare) {
7837         int y, x;
7838         for (y = toY-1; y <= toY+1; y++) {
7839           for (x = toX-1; x <= toX+1; x++) {
7840             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7841                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7842               board[y][x] = EmptySquare;
7843             }
7844           }
7845         }
7846         board[toY][toX] = EmptySquare;
7847       }
7848     }
7849     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7850         /* [HGM] Shogi promotions */
7851         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7852     }
7853
7854     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7855                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7856         // [HGM] superchess: take promotion piece out of holdings
7857         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7858         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7859             if(!--board[k][BOARD_WIDTH-2])
7860                 board[k][BOARD_WIDTH-1] = EmptySquare;
7861         } else {
7862             if(!--board[BOARD_HEIGHT-1-k][1])
7863                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7864         }
7865     }
7866
7867 }
7868
7869 /* Updates forwardMostMove */
7870 void
7871 MakeMove(fromX, fromY, toX, toY, promoChar)
7872      int fromX, fromY, toX, toY;
7873      int promoChar;
7874 {
7875 //    forwardMostMove++; // [HGM] bare: moved downstream
7876
7877     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7878         int timeLeft; static int lastLoadFlag=0; int king, piece;
7879         piece = boards[forwardMostMove][fromY][fromX];
7880         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7881         if(gameInfo.variant == VariantKnightmate)
7882             king += (int) WhiteUnicorn - (int) WhiteKing;
7883         if(forwardMostMove == 0) {
7884             if(blackPlaysFirst) 
7885                 fprintf(serverMoves, "%s;", second.tidy);
7886             fprintf(serverMoves, "%s;", first.tidy);
7887             if(!blackPlaysFirst) 
7888                 fprintf(serverMoves, "%s;", second.tidy);
7889         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7890         lastLoadFlag = loadFlag;
7891         // print base move
7892         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7893         // print castling suffix
7894         if( toY == fromY && piece == king ) {
7895             if(toX-fromX > 1)
7896                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7897             if(fromX-toX >1)
7898                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7899         }
7900         // e.p. suffix
7901         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7902              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7903              boards[forwardMostMove][toY][toX] == EmptySquare
7904              && fromX != toX )
7905                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7906         // promotion suffix
7907         if(promoChar != NULLCHAR)
7908                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7909         if(!loadFlag) {
7910             fprintf(serverMoves, "/%d/%d",
7911                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7912             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7913             else                      timeLeft = blackTimeRemaining/1000;
7914             fprintf(serverMoves, "/%d", timeLeft);
7915         }
7916         fflush(serverMoves);
7917     }
7918
7919     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7920       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7921                         0, 1);
7922       return;
7923     }
7924     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7925     if (commentList[forwardMostMove+1] != NULL) {
7926         free(commentList[forwardMostMove+1]);
7927         commentList[forwardMostMove+1] = NULL;
7928     }
7929     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7930     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7931     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7932     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7933     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7934     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7935     gameInfo.result = GameUnfinished;
7936     if (gameInfo.resultDetails != NULL) {
7937         free(gameInfo.resultDetails);
7938         gameInfo.resultDetails = NULL;
7939     }
7940     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7941                               moveList[forwardMostMove - 1]);
7942     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7943                              PosFlags(forwardMostMove - 1),
7944                              fromY, fromX, toY, toX, promoChar,
7945                              parseList[forwardMostMove - 1]);
7946     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7947       case MT_NONE:
7948       case MT_STALEMATE:
7949       default:
7950         break;
7951       case MT_CHECK:
7952         if(gameInfo.variant != VariantShogi)
7953             strcat(parseList[forwardMostMove - 1], "+");
7954         break;
7955       case MT_CHECKMATE:
7956       case MT_STAINMATE:
7957         strcat(parseList[forwardMostMove - 1], "#");
7958         break;
7959     }
7960     if (appData.debugMode) {
7961         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7962     }
7963
7964 }
7965
7966 /* Updates currentMove if not pausing */
7967 void
7968 ShowMove(fromX, fromY, toX, toY)
7969 {
7970     int instant = (gameMode == PlayFromGameFile) ?
7971         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7972     if(appData.noGUI) return;
7973     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7974         if (!instant) {
7975             if (forwardMostMove == currentMove + 1) {
7976                 AnimateMove(boards[forwardMostMove - 1],
7977                             fromX, fromY, toX, toY);
7978             }
7979             if (appData.highlightLastMove) {
7980                 SetHighlights(fromX, fromY, toX, toY);
7981             }
7982         }
7983         currentMove = forwardMostMove;
7984     }
7985
7986     if (instant) return;
7987
7988     DisplayMove(currentMove - 1);
7989     DrawPosition(FALSE, boards[currentMove]);
7990     DisplayBothClocks();
7991     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7992 }
7993
7994 void SendEgtPath(ChessProgramState *cps)
7995 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7996         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7997
7998         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7999
8000         while(*p) {
8001             char c, *q = name+1, *r, *s;
8002
8003             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8004             while(*p && *p != ',') *q++ = *p++;
8005             *q++ = ':'; *q = 0;
8006             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8007                 strcmp(name, ",nalimov:") == 0 ) {
8008                 // take nalimov path from the menu-changeable option first, if it is defined
8009                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8010                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8011             } else
8012             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8013                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8014                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8015                 s = r = StrStr(s, ":") + 1; // beginning of path info
8016                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8017                 c = *r; *r = 0;             // temporarily null-terminate path info
8018                     *--q = 0;               // strip of trailig ':' from name
8019                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8020                 *r = c;
8021                 SendToProgram(buf,cps);     // send egtbpath command for this format
8022             }
8023             if(*p == ',') p++; // read away comma to position for next format name
8024         }
8025 }
8026
8027 void
8028 InitChessProgram(cps, setup)
8029      ChessProgramState *cps;
8030      int setup; /* [HGM] needed to setup FRC opening position */
8031 {
8032     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8033     if (appData.noChessProgram) return;
8034     hintRequested = FALSE;
8035     bookRequested = FALSE;
8036
8037     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8038     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8039     if(cps->memSize) { /* [HGM] memory */
8040         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8041         SendToProgram(buf, cps);
8042     }
8043     SendEgtPath(cps); /* [HGM] EGT */
8044     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8045         sprintf(buf, "cores %d\n", appData.smpCores);
8046         SendToProgram(buf, cps);
8047     }
8048
8049     SendToProgram(cps->initString, cps);
8050     if (gameInfo.variant != VariantNormal &&
8051         gameInfo.variant != VariantLoadable
8052         /* [HGM] also send variant if board size non-standard */
8053         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8054                                             ) {
8055       char *v = VariantName(gameInfo.variant);
8056       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8057         /* [HGM] in protocol 1 we have to assume all variants valid */
8058         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8059         DisplayFatalError(buf, 0, 1);
8060         return;
8061       }
8062
8063       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8064       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8065       if( gameInfo.variant == VariantXiangqi )
8066            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8067       if( gameInfo.variant == VariantShogi )
8068            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8069       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8070            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8071       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8072                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8073            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8074       if( gameInfo.variant == VariantCourier )
8075            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8076       if( gameInfo.variant == VariantSuper )
8077            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8078       if( gameInfo.variant == VariantGreat )
8079            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8080
8081       if(overruled) {
8082            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8083                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8084            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8085            if(StrStr(cps->variants, b) == NULL) { 
8086                // specific sized variant not known, check if general sizing allowed
8087                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8088                    if(StrStr(cps->variants, "boardsize") == NULL) {
8089                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8090                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8091                        DisplayFatalError(buf, 0, 1);
8092                        return;
8093                    }
8094                    /* [HGM] here we really should compare with the maximum supported board size */
8095                }
8096            }
8097       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8098       sprintf(buf, "variant %s\n", b);
8099       SendToProgram(buf, cps);
8100     }
8101     currentlyInitializedVariant = gameInfo.variant;
8102
8103     /* [HGM] send opening position in FRC to first engine */
8104     if(setup) {
8105           SendToProgram("force\n", cps);
8106           SendBoard(cps, 0);
8107           /* engine is now in force mode! Set flag to wake it up after first move. */
8108           setboardSpoiledMachineBlack = 1;
8109     }
8110
8111     if (cps->sendICS) {
8112       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8113       SendToProgram(buf, cps);
8114     }
8115     cps->maybeThinking = FALSE;
8116     cps->offeredDraw = 0;
8117     if (!appData.icsActive) {
8118         SendTimeControl(cps, movesPerSession, timeControl,
8119                         timeIncrement, appData.searchDepth,
8120                         searchTime);
8121     }
8122     if (appData.showThinking 
8123         // [HGM] thinking: four options require thinking output to be sent
8124         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8125                                 ) {
8126         SendToProgram("post\n", cps);
8127     }
8128     SendToProgram("hard\n", cps);
8129     if (!appData.ponderNextMove) {
8130         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8131            it without being sure what state we are in first.  "hard"
8132            is not a toggle, so that one is OK.
8133          */
8134         SendToProgram("easy\n", cps);
8135     }
8136     if (cps->usePing) {
8137       sprintf(buf, "ping %d\n", ++cps->lastPing);
8138       SendToProgram(buf, cps);
8139     }
8140     cps->initDone = TRUE;
8141 }   
8142
8143
8144 void
8145 StartChessProgram(cps)
8146      ChessProgramState *cps;
8147 {
8148     char buf[MSG_SIZ];
8149     int err;
8150
8151     if (appData.noChessProgram) return;
8152     cps->initDone = FALSE;
8153
8154     if (strcmp(cps->host, "localhost") == 0) {
8155         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8156     } else if (*appData.remoteShell == NULLCHAR) {
8157         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8158     } else {
8159         if (*appData.remoteUser == NULLCHAR) {
8160           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8161                     cps->program);
8162         } else {
8163           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8164                     cps->host, appData.remoteUser, cps->program);
8165         }
8166         err = StartChildProcess(buf, "", &cps->pr);
8167     }
8168     
8169     if (err != 0) {
8170         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8171         DisplayFatalError(buf, err, 1);
8172         cps->pr = NoProc;
8173         cps->isr = NULL;
8174         return;
8175     }
8176     
8177     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8178     if (cps->protocolVersion > 1) {
8179       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8180       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8181       cps->comboCnt = 0;  //                and values of combo boxes
8182       SendToProgram(buf, cps);
8183     } else {
8184       SendToProgram("xboard\n", cps);
8185     }
8186 }
8187
8188
8189 void
8190 TwoMachinesEventIfReady P((void))
8191 {
8192   if (first.lastPing != first.lastPong) {
8193     DisplayMessage("", _("Waiting for first chess program"));
8194     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8195     return;
8196   }
8197   if (second.lastPing != second.lastPong) {
8198     DisplayMessage("", _("Waiting for second chess program"));
8199     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8200     return;
8201   }
8202   ThawUI();
8203   TwoMachinesEvent();
8204 }
8205
8206 void
8207 NextMatchGame P((void))
8208 {
8209     int index; /* [HGM] autoinc: step load index during match */
8210     Reset(FALSE, TRUE);
8211     if (*appData.loadGameFile != NULLCHAR) {
8212         index = appData.loadGameIndex;
8213         if(index < 0) { // [HGM] autoinc
8214             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8215             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8216         } 
8217         LoadGameFromFile(appData.loadGameFile,
8218                          index,
8219                          appData.loadGameFile, FALSE);
8220     } else if (*appData.loadPositionFile != NULLCHAR) {
8221         index = appData.loadPositionIndex;
8222         if(index < 0) { // [HGM] autoinc
8223             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8224             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8225         } 
8226         LoadPositionFromFile(appData.loadPositionFile,
8227                              index,
8228                              appData.loadPositionFile);
8229     }
8230     TwoMachinesEventIfReady();
8231 }
8232
8233 void UserAdjudicationEvent( int result )
8234 {
8235     ChessMove gameResult = GameIsDrawn;
8236
8237     if( result > 0 ) {
8238         gameResult = WhiteWins;
8239     }
8240     else if( result < 0 ) {
8241         gameResult = BlackWins;
8242     }
8243
8244     if( gameMode == TwoMachinesPlay ) {
8245         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8246     }
8247 }
8248
8249
8250 // [HGM] save: calculate checksum of game to make games easily identifiable
8251 int StringCheckSum(char *s)
8252 {
8253         int i = 0;
8254         if(s==NULL) return 0;
8255         while(*s) i = i*259 + *s++;
8256         return i;
8257 }
8258
8259 int GameCheckSum()
8260 {
8261         int i, sum=0;
8262         for(i=backwardMostMove; i<forwardMostMove; i++) {
8263                 sum += pvInfoList[i].depth;
8264                 sum += StringCheckSum(parseList[i]);
8265                 sum += StringCheckSum(commentList[i]);
8266                 sum *= 261;
8267         }
8268         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8269         return sum + StringCheckSum(commentList[i]);
8270 } // end of save patch
8271
8272 void
8273 GameEnds(result, resultDetails, whosays)
8274      ChessMove result;
8275      char *resultDetails;
8276      int whosays;
8277 {
8278     GameMode nextGameMode;
8279     int isIcsGame;
8280     char buf[MSG_SIZ];
8281
8282     if(endingGame) return; /* [HGM] crash: forbid recursion */
8283     endingGame = 1;
8284
8285     if (appData.debugMode) {
8286       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8287               result, resultDetails ? resultDetails : "(null)", whosays);
8288     }
8289
8290     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8291         /* If we are playing on ICS, the server decides when the
8292            game is over, but the engine can offer to draw, claim 
8293            a draw, or resign. 
8294          */
8295 #if ZIPPY
8296         if (appData.zippyPlay && first.initDone) {
8297             if (result == GameIsDrawn) {
8298                 /* In case draw still needs to be claimed */
8299                 SendToICS(ics_prefix);
8300                 SendToICS("draw\n");
8301             } else if (StrCaseStr(resultDetails, "resign")) {
8302                 SendToICS(ics_prefix);
8303                 SendToICS("resign\n");
8304             }
8305         }
8306 #endif
8307         endingGame = 0; /* [HGM] crash */
8308         return;
8309     }
8310
8311     /* If we're loading the game from a file, stop */
8312     if (whosays == GE_FILE) {
8313       (void) StopLoadGameTimer();
8314       gameFileFP = NULL;
8315     }
8316
8317     /* Cancel draw offers */
8318     first.offeredDraw = second.offeredDraw = 0;
8319
8320     /* If this is an ICS game, only ICS can really say it's done;
8321        if not, anyone can. */
8322     isIcsGame = (gameMode == IcsPlayingWhite || 
8323                  gameMode == IcsPlayingBlack || 
8324                  gameMode == IcsObserving    || 
8325                  gameMode == IcsExamining);
8326
8327     if (!isIcsGame || whosays == GE_ICS) {
8328         /* OK -- not an ICS game, or ICS said it was done */
8329         StopClocks();
8330         if (!isIcsGame && !appData.noChessProgram) 
8331           SetUserThinkingEnables();
8332     
8333         /* [HGM] if a machine claims the game end we verify this claim */
8334         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8335             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8336                 char claimer;
8337                 ChessMove trueResult = (ChessMove) -1;
8338
8339                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8340                                             first.twoMachinesColor[0] :
8341                                             second.twoMachinesColor[0] ;
8342
8343                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8344                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8345                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8346                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8347                 } else
8348                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8349                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8350                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8351                 } else
8352                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8353                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8354                 }
8355
8356                 // now verify win claims, but not in drop games, as we don't understand those yet
8357                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8358                                                  || gameInfo.variant == VariantGreat) &&
8359                     (result == WhiteWins && claimer == 'w' ||
8360                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8361                       if (appData.debugMode) {
8362                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8363                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8364                       }
8365                       if(result != trueResult) {
8366                               sprintf(buf, "False win claim: '%s'", resultDetails);
8367                               result = claimer == 'w' ? BlackWins : WhiteWins;
8368                               resultDetails = buf;
8369                       }
8370                 } else
8371                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8372                     && (forwardMostMove <= backwardMostMove ||
8373                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8374                         (claimer=='b')==(forwardMostMove&1))
8375                                                                                   ) {
8376                       /* [HGM] verify: draws that were not flagged are false claims */
8377                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8378                       result = claimer == 'w' ? BlackWins : WhiteWins;
8379                       resultDetails = buf;
8380                 }
8381                 /* (Claiming a loss is accepted no questions asked!) */
8382             }
8383             /* [HGM] bare: don't allow bare King to win */
8384             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8385                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8386                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8387                && result != GameIsDrawn)
8388             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8389                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8390                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8391                         if(p >= 0 && p <= (int)WhiteKing) k++;
8392                 }
8393                 if (appData.debugMode) {
8394                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8395                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8396                 }
8397                 if(k <= 1) {
8398                         result = GameIsDrawn;
8399                         sprintf(buf, "%s but bare king", resultDetails);
8400                         resultDetails = buf;
8401                 }
8402             }
8403         }
8404
8405
8406         if(serverMoves != NULL && !loadFlag) { char c = '=';
8407             if(result==WhiteWins) c = '+';
8408             if(result==BlackWins) c = '-';
8409             if(resultDetails != NULL)
8410                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8411         }
8412         if (resultDetails != NULL) {
8413             gameInfo.result = result;
8414             gameInfo.resultDetails = StrSave(resultDetails);
8415
8416             /* display last move only if game was not loaded from file */
8417             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8418                 DisplayMove(currentMove - 1);
8419     
8420             if (forwardMostMove != 0) {
8421                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8422                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8423                                                                 ) {
8424                     if (*appData.saveGameFile != NULLCHAR) {
8425                         SaveGameToFile(appData.saveGameFile, TRUE);
8426                     } else if (appData.autoSaveGames) {
8427                         AutoSaveGame();
8428                     }
8429                     if (*appData.savePositionFile != NULLCHAR) {
8430                         SavePositionToFile(appData.savePositionFile);
8431                     }
8432                 }
8433             }
8434
8435             /* Tell program how game ended in case it is learning */
8436             /* [HGM] Moved this to after saving the PGN, just in case */
8437             /* engine died and we got here through time loss. In that */
8438             /* case we will get a fatal error writing the pipe, which */
8439             /* would otherwise lose us the PGN.                       */
8440             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8441             /* output during GameEnds should never be fatal anymore   */
8442             if (gameMode == MachinePlaysWhite ||
8443                 gameMode == MachinePlaysBlack ||
8444                 gameMode == TwoMachinesPlay ||
8445                 gameMode == IcsPlayingWhite ||
8446                 gameMode == IcsPlayingBlack ||
8447                 gameMode == BeginningOfGame) {
8448                 char buf[MSG_SIZ];
8449                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8450                         resultDetails);
8451                 if (first.pr != NoProc) {
8452                     SendToProgram(buf, &first);
8453                 }
8454                 if (second.pr != NoProc &&
8455                     gameMode == TwoMachinesPlay) {
8456                     SendToProgram(buf, &second);
8457                 }
8458             }
8459         }
8460
8461         if (appData.icsActive) {
8462             if (appData.quietPlay &&
8463                 (gameMode == IcsPlayingWhite ||
8464                  gameMode == IcsPlayingBlack)) {
8465                 SendToICS(ics_prefix);
8466                 SendToICS("set shout 1\n");
8467             }
8468             nextGameMode = IcsIdle;
8469             ics_user_moved = FALSE;
8470             /* clean up premove.  It's ugly when the game has ended and the
8471              * premove highlights are still on the board.
8472              */
8473             if (gotPremove) {
8474               gotPremove = FALSE;
8475               ClearPremoveHighlights();
8476               DrawPosition(FALSE, boards[currentMove]);
8477             }
8478             if (whosays == GE_ICS) {
8479                 switch (result) {
8480                 case WhiteWins:
8481                     if (gameMode == IcsPlayingWhite)
8482                         PlayIcsWinSound();
8483                     else if(gameMode == IcsPlayingBlack)
8484                         PlayIcsLossSound();
8485                     break;
8486                 case BlackWins:
8487                     if (gameMode == IcsPlayingBlack)
8488                         PlayIcsWinSound();
8489                     else if(gameMode == IcsPlayingWhite)
8490                         PlayIcsLossSound();
8491                     break;
8492                 case GameIsDrawn:
8493                     PlayIcsDrawSound();
8494                     break;
8495                 default:
8496                     PlayIcsUnfinishedSound();
8497                 }
8498             }
8499         } else if (gameMode == EditGame ||
8500                    gameMode == PlayFromGameFile || 
8501                    gameMode == AnalyzeMode || 
8502                    gameMode == AnalyzeFile) {
8503             nextGameMode = gameMode;
8504         } else {
8505             nextGameMode = EndOfGame;
8506         }
8507         pausing = FALSE;
8508         ModeHighlight();
8509     } else {
8510         nextGameMode = gameMode;
8511     }
8512
8513     if (appData.noChessProgram) {
8514         gameMode = nextGameMode;
8515         ModeHighlight();
8516         endingGame = 0; /* [HGM] crash */
8517         return;
8518     }
8519
8520     if (first.reuse) {
8521         /* Put first chess program into idle state */
8522         if (first.pr != NoProc &&
8523             (gameMode == MachinePlaysWhite ||
8524              gameMode == MachinePlaysBlack ||
8525              gameMode == TwoMachinesPlay ||
8526              gameMode == IcsPlayingWhite ||
8527              gameMode == IcsPlayingBlack ||
8528              gameMode == BeginningOfGame)) {
8529             SendToProgram("force\n", &first);
8530             if (first.usePing) {
8531               char buf[MSG_SIZ];
8532               sprintf(buf, "ping %d\n", ++first.lastPing);
8533               SendToProgram(buf, &first);
8534             }
8535         }
8536     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8537         /* Kill off first chess program */
8538         if (first.isr != NULL)
8539           RemoveInputSource(first.isr);
8540         first.isr = NULL;
8541     
8542         if (first.pr != NoProc) {
8543             ExitAnalyzeMode();
8544             DoSleep( appData.delayBeforeQuit );
8545             SendToProgram("quit\n", &first);
8546             DoSleep( appData.delayAfterQuit );
8547             DestroyChildProcess(first.pr, first.useSigterm);
8548         }
8549         first.pr = NoProc;
8550     }
8551     if (second.reuse) {
8552         /* Put second chess program into idle state */
8553         if (second.pr != NoProc &&
8554             gameMode == TwoMachinesPlay) {
8555             SendToProgram("force\n", &second);
8556             if (second.usePing) {
8557               char buf[MSG_SIZ];
8558               sprintf(buf, "ping %d\n", ++second.lastPing);
8559               SendToProgram(buf, &second);
8560             }
8561         }
8562     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8563         /* Kill off second chess program */
8564         if (second.isr != NULL)
8565           RemoveInputSource(second.isr);
8566         second.isr = NULL;
8567     
8568         if (second.pr != NoProc) {
8569             DoSleep( appData.delayBeforeQuit );
8570             SendToProgram("quit\n", &second);
8571             DoSleep( appData.delayAfterQuit );
8572             DestroyChildProcess(second.pr, second.useSigterm);
8573         }
8574         second.pr = NoProc;
8575     }
8576
8577     if (matchMode && gameMode == TwoMachinesPlay) {
8578         switch (result) {
8579         case WhiteWins:
8580           if (first.twoMachinesColor[0] == 'w') {
8581             first.matchWins++;
8582           } else {
8583             second.matchWins++;
8584           }
8585           break;
8586         case BlackWins:
8587           if (first.twoMachinesColor[0] == 'b') {
8588             first.matchWins++;
8589           } else {
8590             second.matchWins++;
8591           }
8592           break;
8593         default:
8594           break;
8595         }
8596         if (matchGame < appData.matchGames) {
8597             char *tmp;
8598             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8599                 tmp = first.twoMachinesColor;
8600                 first.twoMachinesColor = second.twoMachinesColor;
8601                 second.twoMachinesColor = tmp;
8602             }
8603             gameMode = nextGameMode;
8604             matchGame++;
8605             if(appData.matchPause>10000 || appData.matchPause<10)
8606                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8607             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8608             endingGame = 0; /* [HGM] crash */
8609             return;
8610         } else {
8611             char buf[MSG_SIZ];
8612             gameMode = nextGameMode;
8613             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8614                     first.tidy, second.tidy,
8615                     first.matchWins, second.matchWins,
8616                     appData.matchGames - (first.matchWins + second.matchWins));
8617             DisplayFatalError(buf, 0, 0);
8618         }
8619     }
8620     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8621         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8622       ExitAnalyzeMode();
8623     gameMode = nextGameMode;
8624     ModeHighlight();
8625     endingGame = 0;  /* [HGM] crash */
8626 }
8627
8628 /* Assumes program was just initialized (initString sent).
8629    Leaves program in force mode. */
8630 void
8631 FeedMovesToProgram(cps, upto) 
8632      ChessProgramState *cps;
8633      int upto;
8634 {
8635     int i;
8636     
8637     if (appData.debugMode)
8638       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8639               startedFromSetupPosition ? "position and " : "",
8640               backwardMostMove, upto, cps->which);
8641     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8642         // [HGM] variantswitch: make engine aware of new variant
8643         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8644                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8645         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8646         SendToProgram(buf, cps);
8647         currentlyInitializedVariant = gameInfo.variant;
8648     }
8649     SendToProgram("force\n", cps);
8650     if (startedFromSetupPosition) {
8651         SendBoard(cps, backwardMostMove);
8652     if (appData.debugMode) {
8653         fprintf(debugFP, "feedMoves\n");
8654     }
8655     }
8656     for (i = backwardMostMove; i < upto; i++) {
8657         SendMoveToProgram(i, cps);
8658     }
8659 }
8660
8661
8662 void
8663 ResurrectChessProgram()
8664 {
8665      /* The chess program may have exited.
8666         If so, restart it and feed it all the moves made so far. */
8667
8668     if (appData.noChessProgram || first.pr != NoProc) return;
8669     
8670     StartChessProgram(&first);
8671     InitChessProgram(&first, FALSE);
8672     FeedMovesToProgram(&first, currentMove);
8673
8674     if (!first.sendTime) {
8675         /* can't tell gnuchess what its clock should read,
8676            so we bow to its notion. */
8677         ResetClocks();
8678         timeRemaining[0][currentMove] = whiteTimeRemaining;
8679         timeRemaining[1][currentMove] = blackTimeRemaining;
8680     }
8681
8682     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8683                 appData.icsEngineAnalyze) && first.analysisSupport) {
8684       SendToProgram("analyze\n", &first);
8685       first.analyzing = TRUE;
8686     }
8687 }
8688
8689 /*
8690  * Button procedures
8691  */
8692 void
8693 Reset(redraw, init)
8694      int redraw, init;
8695 {
8696     int i;
8697
8698     if (appData.debugMode) {
8699         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8700                 redraw, init, gameMode);
8701     }
8702     CleanupTail(); // [HGM] vari: delete any stored variations
8703     pausing = pauseExamInvalid = FALSE;
8704     startedFromSetupPosition = blackPlaysFirst = FALSE;
8705     firstMove = TRUE;
8706     whiteFlag = blackFlag = FALSE;
8707     userOfferedDraw = FALSE;
8708     hintRequested = bookRequested = FALSE;
8709     first.maybeThinking = FALSE;
8710     second.maybeThinking = FALSE;
8711     first.bookSuspend = FALSE; // [HGM] book
8712     second.bookSuspend = FALSE;
8713     thinkOutput[0] = NULLCHAR;
8714     lastHint[0] = NULLCHAR;
8715     ClearGameInfo(&gameInfo);
8716     gameInfo.variant = StringToVariant(appData.variant);
8717     ics_user_moved = ics_clock_paused = FALSE;
8718     ics_getting_history = H_FALSE;
8719     ics_gamenum = -1;
8720     white_holding[0] = black_holding[0] = NULLCHAR;
8721     ClearProgramStats();
8722     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8723     
8724     ResetFrontEnd();
8725     ClearHighlights();
8726     flipView = appData.flipView;
8727     ClearPremoveHighlights();
8728     gotPremove = FALSE;
8729     alarmSounded = FALSE;
8730
8731     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8732     if(appData.serverMovesName != NULL) {
8733         /* [HGM] prepare to make moves file for broadcasting */
8734         clock_t t = clock();
8735         if(serverMoves != NULL) fclose(serverMoves);
8736         serverMoves = fopen(appData.serverMovesName, "r");
8737         if(serverMoves != NULL) {
8738             fclose(serverMoves);
8739             /* delay 15 sec before overwriting, so all clients can see end */
8740             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8741         }
8742         serverMoves = fopen(appData.serverMovesName, "w");
8743     }
8744
8745     ExitAnalyzeMode();
8746     gameMode = BeginningOfGame;
8747     ModeHighlight();
8748     if(appData.icsActive) gameInfo.variant = VariantNormal;
8749     currentMove = forwardMostMove = backwardMostMove = 0;
8750     InitPosition(redraw);
8751     for (i = 0; i < MAX_MOVES; i++) {
8752         if (commentList[i] != NULL) {
8753             free(commentList[i]);
8754             commentList[i] = NULL;
8755         }
8756     }
8757     ResetClocks();
8758     timeRemaining[0][0] = whiteTimeRemaining;
8759     timeRemaining[1][0] = blackTimeRemaining;
8760     if (first.pr == NULL) {
8761         StartChessProgram(&first);
8762     }
8763     if (init) {
8764             InitChessProgram(&first, startedFromSetupPosition);
8765     }
8766     DisplayTitle("");
8767     DisplayMessage("", "");
8768     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8769     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8770 }
8771
8772 void
8773 AutoPlayGameLoop()
8774 {
8775     for (;;) {
8776         if (!AutoPlayOneMove())
8777           return;
8778         if (matchMode || appData.timeDelay == 0)
8779           continue;
8780         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8781           return;
8782         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8783         break;
8784     }
8785 }
8786
8787
8788 int
8789 AutoPlayOneMove()
8790 {
8791     int fromX, fromY, toX, toY;
8792
8793     if (appData.debugMode) {
8794       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8795     }
8796
8797     if (gameMode != PlayFromGameFile)
8798       return FALSE;
8799
8800     if (currentMove >= forwardMostMove) {
8801       gameMode = EditGame;
8802       ModeHighlight();
8803
8804       /* [AS] Clear current move marker at the end of a game */
8805       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8806
8807       return FALSE;
8808     }
8809     
8810     toX = moveList[currentMove][2] - AAA;
8811     toY = moveList[currentMove][3] - ONE;
8812
8813     if (moveList[currentMove][1] == '@') {
8814         if (appData.highlightLastMove) {
8815             SetHighlights(-1, -1, toX, toY);
8816         }
8817     } else {
8818         fromX = moveList[currentMove][0] - AAA;
8819         fromY = moveList[currentMove][1] - ONE;
8820
8821         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8822
8823         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8824
8825         if (appData.highlightLastMove) {
8826             SetHighlights(fromX, fromY, toX, toY);
8827         }
8828     }
8829     DisplayMove(currentMove);
8830     SendMoveToProgram(currentMove++, &first);
8831     DisplayBothClocks();
8832     DrawPosition(FALSE, boards[currentMove]);
8833     // [HGM] PV info: always display, routine tests if empty
8834     DisplayComment(currentMove - 1, commentList[currentMove]);
8835     return TRUE;
8836 }
8837
8838
8839 int
8840 LoadGameOneMove(readAhead)
8841      ChessMove readAhead;
8842 {
8843     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8844     char promoChar = NULLCHAR;
8845     ChessMove moveType;
8846     char move[MSG_SIZ];
8847     char *p, *q;
8848     
8849     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8850         gameMode != AnalyzeMode && gameMode != Training) {
8851         gameFileFP = NULL;
8852         return FALSE;
8853     }
8854     
8855     yyboardindex = forwardMostMove;
8856     if (readAhead != (ChessMove)0) {
8857       moveType = readAhead;
8858     } else {
8859       if (gameFileFP == NULL)
8860           return FALSE;
8861       moveType = (ChessMove) yylex();
8862     }
8863     
8864     done = FALSE;
8865     switch (moveType) {
8866       case Comment:
8867         if (appData.debugMode) 
8868           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8869         p = yy_text;
8870
8871         /* append the comment but don't display it */
8872         AppendComment(currentMove, p, FALSE);
8873         return TRUE;
8874
8875       case WhiteCapturesEnPassant:
8876       case BlackCapturesEnPassant:
8877       case WhitePromotionChancellor:
8878       case BlackPromotionChancellor:
8879       case WhitePromotionArchbishop:
8880       case BlackPromotionArchbishop:
8881       case WhitePromotionCentaur:
8882       case BlackPromotionCentaur:
8883       case WhitePromotionQueen:
8884       case BlackPromotionQueen:
8885       case WhitePromotionRook:
8886       case BlackPromotionRook:
8887       case WhitePromotionBishop:
8888       case BlackPromotionBishop:
8889       case WhitePromotionKnight:
8890       case BlackPromotionKnight:
8891       case WhitePromotionKing:
8892       case BlackPromotionKing:
8893       case NormalMove:
8894       case WhiteKingSideCastle:
8895       case WhiteQueenSideCastle:
8896       case BlackKingSideCastle:
8897       case BlackQueenSideCastle:
8898       case WhiteKingSideCastleWild:
8899       case WhiteQueenSideCastleWild:
8900       case BlackKingSideCastleWild:
8901       case BlackQueenSideCastleWild:
8902       /* PUSH Fabien */
8903       case WhiteHSideCastleFR:
8904       case WhiteASideCastleFR:
8905       case BlackHSideCastleFR:
8906       case BlackASideCastleFR:
8907       /* POP Fabien */
8908         if (appData.debugMode)
8909           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8910         fromX = currentMoveString[0] - AAA;
8911         fromY = currentMoveString[1] - ONE;
8912         toX = currentMoveString[2] - AAA;
8913         toY = currentMoveString[3] - ONE;
8914         promoChar = currentMoveString[4];
8915         break;
8916
8917       case WhiteDrop:
8918       case BlackDrop:
8919         if (appData.debugMode)
8920           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8921         fromX = moveType == WhiteDrop ?
8922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8923         (int) CharToPiece(ToLower(currentMoveString[0]));
8924         fromY = DROP_RANK;
8925         toX = currentMoveString[2] - AAA;
8926         toY = currentMoveString[3] - ONE;
8927         break;
8928
8929       case WhiteWins:
8930       case BlackWins:
8931       case GameIsDrawn:
8932       case GameUnfinished:
8933         if (appData.debugMode)
8934           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8935         p = strchr(yy_text, '{');
8936         if (p == NULL) p = strchr(yy_text, '(');
8937         if (p == NULL) {
8938             p = yy_text;
8939             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8940         } else {
8941             q = strchr(p, *p == '{' ? '}' : ')');
8942             if (q != NULL) *q = NULLCHAR;
8943             p++;
8944         }
8945         GameEnds(moveType, p, GE_FILE);
8946         done = TRUE;
8947         if (cmailMsgLoaded) {
8948             ClearHighlights();
8949             flipView = WhiteOnMove(currentMove);
8950             if (moveType == GameUnfinished) flipView = !flipView;
8951             if (appData.debugMode)
8952               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8953         }
8954         break;
8955
8956       case (ChessMove) 0:       /* end of file */
8957         if (appData.debugMode)
8958           fprintf(debugFP, "Parser hit end of file\n");
8959         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8960           case MT_NONE:
8961           case MT_CHECK:
8962             break;
8963           case MT_CHECKMATE:
8964           case MT_STAINMATE:
8965             if (WhiteOnMove(currentMove)) {
8966                 GameEnds(BlackWins, "Black mates", GE_FILE);
8967             } else {
8968                 GameEnds(WhiteWins, "White mates", GE_FILE);
8969             }
8970             break;
8971           case MT_STALEMATE:
8972             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8973             break;
8974         }
8975         done = TRUE;
8976         break;
8977
8978       case MoveNumberOne:
8979         if (lastLoadGameStart == GNUChessGame) {
8980             /* GNUChessGames have numbers, but they aren't move numbers */
8981             if (appData.debugMode)
8982               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8983                       yy_text, (int) moveType);
8984             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8985         }
8986         /* else fall thru */
8987
8988       case XBoardGame:
8989       case GNUChessGame:
8990       case PGNTag:
8991         /* Reached start of next game in file */
8992         if (appData.debugMode)
8993           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8994         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8995           case MT_NONE:
8996           case MT_CHECK:
8997             break;
8998           case MT_CHECKMATE:
8999           case MT_STAINMATE:
9000             if (WhiteOnMove(currentMove)) {
9001                 GameEnds(BlackWins, "Black mates", GE_FILE);
9002             } else {
9003                 GameEnds(WhiteWins, "White mates", GE_FILE);
9004             }
9005             break;
9006           case MT_STALEMATE:
9007             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9008             break;
9009         }
9010         done = TRUE;
9011         break;
9012
9013       case PositionDiagram:     /* should not happen; ignore */
9014       case ElapsedTime:         /* ignore */
9015       case NAG:                 /* ignore */
9016         if (appData.debugMode)
9017           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9018                   yy_text, (int) moveType);
9019         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9020
9021       case IllegalMove:
9022         if (appData.testLegality) {
9023             if (appData.debugMode)
9024               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9025             sprintf(move, _("Illegal move: %d.%s%s"),
9026                     (forwardMostMove / 2) + 1,
9027                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9028             DisplayError(move, 0);
9029             done = TRUE;
9030         } else {
9031             if (appData.debugMode)
9032               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9033                       yy_text, currentMoveString);
9034             fromX = currentMoveString[0] - AAA;
9035             fromY = currentMoveString[1] - ONE;
9036             toX = currentMoveString[2] - AAA;
9037             toY = currentMoveString[3] - ONE;
9038             promoChar = currentMoveString[4];
9039         }
9040         break;
9041
9042       case AmbiguousMove:
9043         if (appData.debugMode)
9044           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9045         sprintf(move, _("Ambiguous move: %d.%s%s"),
9046                 (forwardMostMove / 2) + 1,
9047                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9048         DisplayError(move, 0);
9049         done = TRUE;
9050         break;
9051
9052       default:
9053       case ImpossibleMove:
9054         if (appData.debugMode)
9055           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9056         sprintf(move, _("Illegal move: %d.%s%s"),
9057                 (forwardMostMove / 2) + 1,
9058                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9059         DisplayError(move, 0);
9060         done = TRUE;
9061         break;
9062     }
9063
9064     if (done) {
9065         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9066             DrawPosition(FALSE, boards[currentMove]);
9067             DisplayBothClocks();
9068             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9069               DisplayComment(currentMove - 1, commentList[currentMove]);
9070         }
9071         (void) StopLoadGameTimer();
9072         gameFileFP = NULL;
9073         cmailOldMove = forwardMostMove;
9074         return FALSE;
9075     } else {
9076         /* currentMoveString is set as a side-effect of yylex */
9077         strcat(currentMoveString, "\n");
9078         strcpy(moveList[forwardMostMove], currentMoveString);
9079         
9080         thinkOutput[0] = NULLCHAR;
9081         MakeMove(fromX, fromY, toX, toY, promoChar);
9082         currentMove = forwardMostMove;
9083         return TRUE;
9084     }
9085 }
9086
9087 /* Load the nth game from the given file */
9088 int
9089 LoadGameFromFile(filename, n, title, useList)
9090      char *filename;
9091      int n;
9092      char *title;
9093      /*Boolean*/ int useList;
9094 {
9095     FILE *f;
9096     char buf[MSG_SIZ];
9097
9098     if (strcmp(filename, "-") == 0) {
9099         f = stdin;
9100         title = "stdin";
9101     } else {
9102         f = fopen(filename, "rb");
9103         if (f == NULL) {
9104           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9105             DisplayError(buf, errno);
9106             return FALSE;
9107         }
9108     }
9109     if (fseek(f, 0, 0) == -1) {
9110         /* f is not seekable; probably a pipe */
9111         useList = FALSE;
9112     }
9113     if (useList && n == 0) {
9114         int error = GameListBuild(f);
9115         if (error) {
9116             DisplayError(_("Cannot build game list"), error);
9117         } else if (!ListEmpty(&gameList) &&
9118                    ((ListGame *) gameList.tailPred)->number > 1) {
9119             GameListPopUp(f, title);
9120             return TRUE;
9121         }
9122         GameListDestroy();
9123         n = 1;
9124     }
9125     if (n == 0) n = 1;
9126     return LoadGame(f, n, title, FALSE);
9127 }
9128
9129
9130 void
9131 MakeRegisteredMove()
9132 {
9133     int fromX, fromY, toX, toY;
9134     char promoChar;
9135     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9136         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9137           case CMAIL_MOVE:
9138           case CMAIL_DRAW:
9139             if (appData.debugMode)
9140               fprintf(debugFP, "Restoring %s for game %d\n",
9141                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9142     
9143             thinkOutput[0] = NULLCHAR;
9144             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9145             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9146             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9147             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9148             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9149             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9150             MakeMove(fromX, fromY, toX, toY, promoChar);
9151             ShowMove(fromX, fromY, toX, toY);
9152               
9153             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9154               case MT_NONE:
9155               case MT_CHECK:
9156                 break;
9157                 
9158               case MT_CHECKMATE:
9159               case MT_STAINMATE:
9160                 if (WhiteOnMove(currentMove)) {
9161                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9162                 } else {
9163                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9164                 }
9165                 break;
9166                 
9167               case MT_STALEMATE:
9168                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9169                 break;
9170             }
9171
9172             break;
9173             
9174           case CMAIL_RESIGN:
9175             if (WhiteOnMove(currentMove)) {
9176                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9177             } else {
9178                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9179             }
9180             break;
9181             
9182           case CMAIL_ACCEPT:
9183             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9184             break;
9185               
9186           default:
9187             break;
9188         }
9189     }
9190
9191     return;
9192 }
9193
9194 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9195 int
9196 CmailLoadGame(f, gameNumber, title, useList)
9197      FILE *f;
9198      int gameNumber;
9199      char *title;
9200      int useList;
9201 {
9202     int retVal;
9203
9204     if (gameNumber > nCmailGames) {
9205         DisplayError(_("No more games in this message"), 0);
9206         return FALSE;
9207     }
9208     if (f == lastLoadGameFP) {
9209         int offset = gameNumber - lastLoadGameNumber;
9210         if (offset == 0) {
9211             cmailMsg[0] = NULLCHAR;
9212             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9213                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9214                 nCmailMovesRegistered--;
9215             }
9216             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9217             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9218                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9219             }
9220         } else {
9221             if (! RegisterMove()) return FALSE;
9222         }
9223     }
9224
9225     retVal = LoadGame(f, gameNumber, title, useList);
9226
9227     /* Make move registered during previous look at this game, if any */
9228     MakeRegisteredMove();
9229
9230     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9231         commentList[currentMove]
9232           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9233         DisplayComment(currentMove - 1, commentList[currentMove]);
9234     }
9235
9236     return retVal;
9237 }
9238
9239 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9240 int
9241 ReloadGame(offset)
9242      int offset;
9243 {
9244     int gameNumber = lastLoadGameNumber + offset;
9245     if (lastLoadGameFP == NULL) {
9246         DisplayError(_("No game has been loaded yet"), 0);
9247         return FALSE;
9248     }
9249     if (gameNumber <= 0) {
9250         DisplayError(_("Can't back up any further"), 0);
9251         return FALSE;
9252     }
9253     if (cmailMsgLoaded) {
9254         return CmailLoadGame(lastLoadGameFP, gameNumber,
9255                              lastLoadGameTitle, lastLoadGameUseList);
9256     } else {
9257         return LoadGame(lastLoadGameFP, gameNumber,
9258                         lastLoadGameTitle, lastLoadGameUseList);
9259     }
9260 }
9261
9262
9263
9264 /* Load the nth game from open file f */
9265 int
9266 LoadGame(f, gameNumber, title, useList)
9267      FILE *f;
9268      int gameNumber;
9269      char *title;
9270      int useList;
9271 {
9272     ChessMove cm;
9273     char buf[MSG_SIZ];
9274     int gn = gameNumber;
9275     ListGame *lg = NULL;
9276     int numPGNTags = 0;
9277     int err;
9278     GameMode oldGameMode;
9279     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9280
9281     if (appData.debugMode) 
9282         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9283
9284     if (gameMode == Training )
9285         SetTrainingModeOff();
9286
9287     oldGameMode = gameMode;
9288     if (gameMode != BeginningOfGame) {
9289       Reset(FALSE, TRUE);
9290     }
9291
9292     gameFileFP = f;
9293     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9294         fclose(lastLoadGameFP);
9295     }
9296
9297     if (useList) {
9298         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9299         
9300         if (lg) {
9301             fseek(f, lg->offset, 0);
9302             GameListHighlight(gameNumber);
9303             gn = 1;
9304         }
9305         else {
9306             DisplayError(_("Game number out of range"), 0);
9307             return FALSE;
9308         }
9309     } else {
9310         GameListDestroy();
9311         if (fseek(f, 0, 0) == -1) {
9312             if (f == lastLoadGameFP ?
9313                 gameNumber == lastLoadGameNumber + 1 :
9314                 gameNumber == 1) {
9315                 gn = 1;
9316             } else {
9317                 DisplayError(_("Can't seek on game file"), 0);
9318                 return FALSE;
9319             }
9320         }
9321     }
9322     lastLoadGameFP = f;
9323     lastLoadGameNumber = gameNumber;
9324     strcpy(lastLoadGameTitle, title);
9325     lastLoadGameUseList = useList;
9326
9327     yynewfile(f);
9328
9329     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9330       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9331                 lg->gameInfo.black);
9332             DisplayTitle(buf);
9333     } else if (*title != NULLCHAR) {
9334         if (gameNumber > 1) {
9335             sprintf(buf, "%s %d", title, gameNumber);
9336             DisplayTitle(buf);
9337         } else {
9338             DisplayTitle(title);
9339         }
9340     }
9341
9342     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9343         gameMode = PlayFromGameFile;
9344         ModeHighlight();
9345     }
9346
9347     currentMove = forwardMostMove = backwardMostMove = 0;
9348     CopyBoard(boards[0], initialPosition);
9349     StopClocks();
9350
9351     /*
9352      * Skip the first gn-1 games in the file.
9353      * Also skip over anything that precedes an identifiable 
9354      * start of game marker, to avoid being confused by 
9355      * garbage at the start of the file.  Currently 
9356      * recognized start of game markers are the move number "1",
9357      * the pattern "gnuchess .* game", the pattern
9358      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9359      * A game that starts with one of the latter two patterns
9360      * will also have a move number 1, possibly
9361      * following a position diagram.
9362      * 5-4-02: Let's try being more lenient and allowing a game to
9363      * start with an unnumbered move.  Does that break anything?
9364      */
9365     cm = lastLoadGameStart = (ChessMove) 0;
9366     while (gn > 0) {
9367         yyboardindex = forwardMostMove;
9368         cm = (ChessMove) yylex();
9369         switch (cm) {
9370           case (ChessMove) 0:
9371             if (cmailMsgLoaded) {
9372                 nCmailGames = CMAIL_MAX_GAMES - gn;
9373             } else {
9374                 Reset(TRUE, TRUE);
9375                 DisplayError(_("Game not found in file"), 0);
9376             }
9377             return FALSE;
9378
9379           case GNUChessGame:
9380           case XBoardGame:
9381             gn--;
9382             lastLoadGameStart = cm;
9383             break;
9384             
9385           case MoveNumberOne:
9386             switch (lastLoadGameStart) {
9387               case GNUChessGame:
9388               case XBoardGame:
9389               case PGNTag:
9390                 break;
9391               case MoveNumberOne:
9392               case (ChessMove) 0:
9393                 gn--;           /* count this game */
9394                 lastLoadGameStart = cm;
9395                 break;
9396               default:
9397                 /* impossible */
9398                 break;
9399             }
9400             break;
9401
9402           case PGNTag:
9403             switch (lastLoadGameStart) {
9404               case GNUChessGame:
9405               case PGNTag:
9406               case MoveNumberOne:
9407               case (ChessMove) 0:
9408                 gn--;           /* count this game */
9409                 lastLoadGameStart = cm;
9410                 break;
9411               case XBoardGame:
9412                 lastLoadGameStart = cm; /* game counted already */
9413                 break;
9414               default:
9415                 /* impossible */
9416                 break;
9417             }
9418             if (gn > 0) {
9419                 do {
9420                     yyboardindex = forwardMostMove;
9421                     cm = (ChessMove) yylex();
9422                 } while (cm == PGNTag || cm == Comment);
9423             }
9424             break;
9425
9426           case WhiteWins:
9427           case BlackWins:
9428           case GameIsDrawn:
9429             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9430                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9431                     != CMAIL_OLD_RESULT) {
9432                     nCmailResults ++ ;
9433                     cmailResult[  CMAIL_MAX_GAMES
9434                                 - gn - 1] = CMAIL_OLD_RESULT;
9435                 }
9436             }
9437             break;
9438
9439           case NormalMove:
9440             /* Only a NormalMove can be at the start of a game
9441              * without a position diagram. */
9442             if (lastLoadGameStart == (ChessMove) 0) {
9443               gn--;
9444               lastLoadGameStart = MoveNumberOne;
9445             }
9446             break;
9447
9448           default:
9449             break;
9450         }
9451     }
9452     
9453     if (appData.debugMode)
9454       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9455
9456     if (cm == XBoardGame) {
9457         /* Skip any header junk before position diagram and/or move 1 */
9458         for (;;) {
9459             yyboardindex = forwardMostMove;
9460             cm = (ChessMove) yylex();
9461
9462             if (cm == (ChessMove) 0 ||
9463                 cm == GNUChessGame || cm == XBoardGame) {
9464                 /* Empty game; pretend end-of-file and handle later */
9465                 cm = (ChessMove) 0;
9466                 break;
9467             }
9468
9469             if (cm == MoveNumberOne || cm == PositionDiagram ||
9470                 cm == PGNTag || cm == Comment)
9471               break;
9472         }
9473     } else if (cm == GNUChessGame) {
9474         if (gameInfo.event != NULL) {
9475             free(gameInfo.event);
9476         }
9477         gameInfo.event = StrSave(yy_text);
9478     }   
9479
9480     startedFromSetupPosition = FALSE;
9481     while (cm == PGNTag) {
9482         if (appData.debugMode) 
9483           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9484         err = ParsePGNTag(yy_text, &gameInfo);
9485         if (!err) numPGNTags++;
9486
9487         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9488         if(gameInfo.variant != oldVariant) {
9489             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9490             InitPosition(TRUE);
9491             oldVariant = gameInfo.variant;
9492             if (appData.debugMode) 
9493               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9494         }
9495
9496
9497         if (gameInfo.fen != NULL) {
9498           Board initial_position;
9499           startedFromSetupPosition = TRUE;
9500           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9501             Reset(TRUE, TRUE);
9502             DisplayError(_("Bad FEN position in file"), 0);
9503             return FALSE;
9504           }
9505           CopyBoard(boards[0], initial_position);
9506           if (blackPlaysFirst) {
9507             currentMove = forwardMostMove = backwardMostMove = 1;
9508             CopyBoard(boards[1], initial_position);
9509             strcpy(moveList[0], "");
9510             strcpy(parseList[0], "");
9511             timeRemaining[0][1] = whiteTimeRemaining;
9512             timeRemaining[1][1] = blackTimeRemaining;
9513             if (commentList[0] != NULL) {
9514               commentList[1] = commentList[0];
9515               commentList[0] = NULL;
9516             }
9517           } else {
9518             currentMove = forwardMostMove = backwardMostMove = 0;
9519           }
9520           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9521           {   int i;
9522               initialRulePlies = FENrulePlies;
9523               for( i=0; i< nrCastlingRights; i++ )
9524                   initialRights[i] = initial_position[CASTLING][i];
9525           }
9526           yyboardindex = forwardMostMove;
9527           free(gameInfo.fen);
9528           gameInfo.fen = NULL;
9529         }
9530
9531         yyboardindex = forwardMostMove;
9532         cm = (ChessMove) yylex();
9533
9534         /* Handle comments interspersed among the tags */
9535         while (cm == Comment) {
9536             char *p;
9537             if (appData.debugMode) 
9538               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9539             p = yy_text;
9540             AppendComment(currentMove, p, FALSE);
9541             yyboardindex = forwardMostMove;
9542             cm = (ChessMove) yylex();
9543         }
9544     }
9545
9546     /* don't rely on existence of Event tag since if game was
9547      * pasted from clipboard the Event tag may not exist
9548      */
9549     if (numPGNTags > 0){
9550         char *tags;
9551         if (gameInfo.variant == VariantNormal) {
9552           gameInfo.variant = StringToVariant(gameInfo.event);
9553         }
9554         if (!matchMode) {
9555           if( appData.autoDisplayTags ) {
9556             tags = PGNTags(&gameInfo);
9557             TagsPopUp(tags, CmailMsg());
9558             free(tags);
9559           }
9560         }
9561     } else {
9562         /* Make something up, but don't display it now */
9563         SetGameInfo();
9564         TagsPopDown();
9565     }
9566
9567     if (cm == PositionDiagram) {
9568         int i, j;
9569         char *p;
9570         Board initial_position;
9571
9572         if (appData.debugMode)
9573           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9574
9575         if (!startedFromSetupPosition) {
9576             p = yy_text;
9577             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9578               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9579                 switch (*p) {
9580                   case '[':
9581                   case '-':
9582                   case ' ':
9583                   case '\t':
9584                   case '\n':
9585                   case '\r':
9586                     break;
9587                   default:
9588                     initial_position[i][j++] = CharToPiece(*p);
9589                     break;
9590                 }
9591             while (*p == ' ' || *p == '\t' ||
9592                    *p == '\n' || *p == '\r') p++;
9593         
9594             if (strncmp(p, "black", strlen("black"))==0)
9595               blackPlaysFirst = TRUE;
9596             else
9597               blackPlaysFirst = FALSE;
9598             startedFromSetupPosition = TRUE;
9599         
9600             CopyBoard(boards[0], initial_position);
9601             if (blackPlaysFirst) {
9602                 currentMove = forwardMostMove = backwardMostMove = 1;
9603                 CopyBoard(boards[1], initial_position);
9604                 strcpy(moveList[0], "");
9605                 strcpy(parseList[0], "");
9606                 timeRemaining[0][1] = whiteTimeRemaining;
9607                 timeRemaining[1][1] = blackTimeRemaining;
9608                 if (commentList[0] != NULL) {
9609                     commentList[1] = commentList[0];
9610                     commentList[0] = NULL;
9611                 }
9612             } else {
9613                 currentMove = forwardMostMove = backwardMostMove = 0;
9614             }
9615         }
9616         yyboardindex = forwardMostMove;
9617         cm = (ChessMove) yylex();
9618     }
9619
9620     if (first.pr == NoProc) {
9621         StartChessProgram(&first);
9622     }
9623     InitChessProgram(&first, FALSE);
9624     SendToProgram("force\n", &first);
9625     if (startedFromSetupPosition) {
9626         SendBoard(&first, forwardMostMove);
9627     if (appData.debugMode) {
9628         fprintf(debugFP, "Load Game\n");
9629     }
9630         DisplayBothClocks();
9631     }      
9632
9633     /* [HGM] server: flag to write setup moves in broadcast file as one */
9634     loadFlag = appData.suppressLoadMoves;
9635
9636     while (cm == Comment) {
9637         char *p;
9638         if (appData.debugMode) 
9639           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9640         p = yy_text;
9641         AppendComment(currentMove, p, FALSE);
9642         yyboardindex = forwardMostMove;
9643         cm = (ChessMove) yylex();
9644     }
9645
9646     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9647         cm == WhiteWins || cm == BlackWins ||
9648         cm == GameIsDrawn || cm == GameUnfinished) {
9649         DisplayMessage("", _("No moves in game"));
9650         if (cmailMsgLoaded) {
9651             if (appData.debugMode)
9652               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9653             ClearHighlights();
9654             flipView = FALSE;
9655         }
9656         DrawPosition(FALSE, boards[currentMove]);
9657         DisplayBothClocks();
9658         gameMode = EditGame;
9659         ModeHighlight();
9660         gameFileFP = NULL;
9661         cmailOldMove = 0;
9662         return TRUE;
9663     }
9664
9665     // [HGM] PV info: routine tests if comment empty
9666     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9667         DisplayComment(currentMove - 1, commentList[currentMove]);
9668     }
9669     if (!matchMode && appData.timeDelay != 0) 
9670       DrawPosition(FALSE, boards[currentMove]);
9671
9672     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9673       programStats.ok_to_send = 1;
9674     }
9675
9676     /* if the first token after the PGN tags is a move
9677      * and not move number 1, retrieve it from the parser 
9678      */
9679     if (cm != MoveNumberOne)
9680         LoadGameOneMove(cm);
9681
9682     /* load the remaining moves from the file */
9683     while (LoadGameOneMove((ChessMove)0)) {
9684       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9685       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9686     }
9687
9688     /* rewind to the start of the game */
9689     currentMove = backwardMostMove;
9690
9691     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9692
9693     if (oldGameMode == AnalyzeFile ||
9694         oldGameMode == AnalyzeMode) {
9695       AnalyzeFileEvent();
9696     }
9697
9698     if (matchMode || appData.timeDelay == 0) {
9699       ToEndEvent();
9700       gameMode = EditGame;
9701       ModeHighlight();
9702     } else if (appData.timeDelay > 0) {
9703       AutoPlayGameLoop();
9704     }
9705
9706     if (appData.debugMode) 
9707         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9708
9709     loadFlag = 0; /* [HGM] true game starts */
9710     return TRUE;
9711 }
9712
9713 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9714 int
9715 ReloadPosition(offset)
9716      int offset;
9717 {
9718     int positionNumber = lastLoadPositionNumber + offset;
9719     if (lastLoadPositionFP == NULL) {
9720         DisplayError(_("No position has been loaded yet"), 0);
9721         return FALSE;
9722     }
9723     if (positionNumber <= 0) {
9724         DisplayError(_("Can't back up any further"), 0);
9725         return FALSE;
9726     }
9727     return LoadPosition(lastLoadPositionFP, positionNumber,
9728                         lastLoadPositionTitle);
9729 }
9730
9731 /* Load the nth position from the given file */
9732 int
9733 LoadPositionFromFile(filename, n, title)
9734      char *filename;
9735      int n;
9736      char *title;
9737 {
9738     FILE *f;
9739     char buf[MSG_SIZ];
9740
9741     if (strcmp(filename, "-") == 0) {
9742         return LoadPosition(stdin, n, "stdin");
9743     } else {
9744         f = fopen(filename, "rb");
9745         if (f == NULL) {
9746             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9747             DisplayError(buf, errno);
9748             return FALSE;
9749         } else {
9750             return LoadPosition(f, n, title);
9751         }
9752     }
9753 }
9754
9755 /* Load the nth position from the given open file, and close it */
9756 int
9757 LoadPosition(f, positionNumber, title)
9758      FILE *f;
9759      int positionNumber;
9760      char *title;
9761 {
9762     char *p, line[MSG_SIZ];
9763     Board initial_position;
9764     int i, j, fenMode, pn;
9765     
9766     if (gameMode == Training )
9767         SetTrainingModeOff();
9768
9769     if (gameMode != BeginningOfGame) {
9770         Reset(FALSE, TRUE);
9771     }
9772     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9773         fclose(lastLoadPositionFP);
9774     }
9775     if (positionNumber == 0) positionNumber = 1;
9776     lastLoadPositionFP = f;
9777     lastLoadPositionNumber = positionNumber;
9778     strcpy(lastLoadPositionTitle, title);
9779     if (first.pr == NoProc) {
9780       StartChessProgram(&first);
9781       InitChessProgram(&first, FALSE);
9782     }    
9783     pn = positionNumber;
9784     if (positionNumber < 0) {
9785         /* Negative position number means to seek to that byte offset */
9786         if (fseek(f, -positionNumber, 0) == -1) {
9787             DisplayError(_("Can't seek on position file"), 0);
9788             return FALSE;
9789         };
9790         pn = 1;
9791     } else {
9792         if (fseek(f, 0, 0) == -1) {
9793             if (f == lastLoadPositionFP ?
9794                 positionNumber == lastLoadPositionNumber + 1 :
9795                 positionNumber == 1) {
9796                 pn = 1;
9797             } else {
9798                 DisplayError(_("Can't seek on position file"), 0);
9799                 return FALSE;
9800             }
9801         }
9802     }
9803     /* See if this file is FEN or old-style xboard */
9804     if (fgets(line, MSG_SIZ, f) == NULL) {
9805         DisplayError(_("Position not found in file"), 0);
9806         return FALSE;
9807     }
9808     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9809     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9810
9811     if (pn >= 2) {
9812         if (fenMode || line[0] == '#') pn--;
9813         while (pn > 0) {
9814             /* skip positions before number pn */
9815             if (fgets(line, MSG_SIZ, f) == NULL) {
9816                 Reset(TRUE, TRUE);
9817                 DisplayError(_("Position not found in file"), 0);
9818                 return FALSE;
9819             }
9820             if (fenMode || line[0] == '#') pn--;
9821         }
9822     }
9823
9824     if (fenMode) {
9825         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9826             DisplayError(_("Bad FEN position in file"), 0);
9827             return FALSE;
9828         }
9829     } else {
9830         (void) fgets(line, MSG_SIZ, f);
9831         (void) fgets(line, MSG_SIZ, f);
9832     
9833         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9834             (void) fgets(line, MSG_SIZ, f);
9835             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9836                 if (*p == ' ')
9837                   continue;
9838                 initial_position[i][j++] = CharToPiece(*p);
9839             }
9840         }
9841     
9842         blackPlaysFirst = FALSE;
9843         if (!feof(f)) {
9844             (void) fgets(line, MSG_SIZ, f);
9845             if (strncmp(line, "black", strlen("black"))==0)
9846               blackPlaysFirst = TRUE;
9847         }
9848     }
9849     startedFromSetupPosition = TRUE;
9850     
9851     SendToProgram("force\n", &first);
9852     CopyBoard(boards[0], initial_position);
9853     if (blackPlaysFirst) {
9854         currentMove = forwardMostMove = backwardMostMove = 1;
9855         strcpy(moveList[0], "");
9856         strcpy(parseList[0], "");
9857         CopyBoard(boards[1], initial_position);
9858         DisplayMessage("", _("Black to play"));
9859     } else {
9860         currentMove = forwardMostMove = backwardMostMove = 0;
9861         DisplayMessage("", _("White to play"));
9862     }
9863     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9864     SendBoard(&first, forwardMostMove);
9865     if (appData.debugMode) {
9866 int i, j;
9867   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9868   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9869         fprintf(debugFP, "Load Position\n");
9870     }
9871
9872     if (positionNumber > 1) {
9873         sprintf(line, "%s %d", title, positionNumber);
9874         DisplayTitle(line);
9875     } else {
9876         DisplayTitle(title);
9877     }
9878     gameMode = EditGame;
9879     ModeHighlight();
9880     ResetClocks();
9881     timeRemaining[0][1] = whiteTimeRemaining;
9882     timeRemaining[1][1] = blackTimeRemaining;
9883     DrawPosition(FALSE, boards[currentMove]);
9884    
9885     return TRUE;
9886 }
9887
9888
9889 void
9890 CopyPlayerNameIntoFileName(dest, src)
9891      char **dest, *src;
9892 {
9893     while (*src != NULLCHAR && *src != ',') {
9894         if (*src == ' ') {
9895             *(*dest)++ = '_';
9896             src++;
9897         } else {
9898             *(*dest)++ = *src++;
9899         }
9900     }
9901 }
9902
9903 char *DefaultFileName(ext)
9904      char *ext;
9905 {
9906     static char def[MSG_SIZ];
9907     char *p;
9908
9909     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9910         p = def;
9911         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9912         *p++ = '-';
9913         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9914         *p++ = '.';
9915         strcpy(p, ext);
9916     } else {
9917         def[0] = NULLCHAR;
9918     }
9919     return def;
9920 }
9921
9922 /* Save the current game to the given file */
9923 int
9924 SaveGameToFile(filename, append)
9925      char *filename;
9926      int append;
9927 {
9928     FILE *f;
9929     char buf[MSG_SIZ];
9930
9931     if (strcmp(filename, "-") == 0) {
9932         return SaveGame(stdout, 0, NULL);
9933     } else {
9934         f = fopen(filename, append ? "a" : "w");
9935         if (f == NULL) {
9936             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9937             DisplayError(buf, errno);
9938             return FALSE;
9939         } else {
9940             return SaveGame(f, 0, NULL);
9941         }
9942     }
9943 }
9944
9945 char *
9946 SavePart(str)
9947      char *str;
9948 {
9949     static char buf[MSG_SIZ];
9950     char *p;
9951     
9952     p = strchr(str, ' ');
9953     if (p == NULL) return str;
9954     strncpy(buf, str, p - str);
9955     buf[p - str] = NULLCHAR;
9956     return buf;
9957 }
9958
9959 #define PGN_MAX_LINE 75
9960
9961 #define PGN_SIDE_WHITE  0
9962 #define PGN_SIDE_BLACK  1
9963
9964 /* [AS] */
9965 static int FindFirstMoveOutOfBook( int side )
9966 {
9967     int result = -1;
9968
9969     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9970         int index = backwardMostMove;
9971         int has_book_hit = 0;
9972
9973         if( (index % 2) != side ) {
9974             index++;
9975         }
9976
9977         while( index < forwardMostMove ) {
9978             /* Check to see if engine is in book */
9979             int depth = pvInfoList[index].depth;
9980             int score = pvInfoList[index].score;
9981             int in_book = 0;
9982
9983             if( depth <= 2 ) {
9984                 in_book = 1;
9985             }
9986             else if( score == 0 && depth == 63 ) {
9987                 in_book = 1; /* Zappa */
9988             }
9989             else if( score == 2 && depth == 99 ) {
9990                 in_book = 1; /* Abrok */
9991             }
9992
9993             has_book_hit += in_book;
9994
9995             if( ! in_book ) {
9996                 result = index;
9997
9998                 break;
9999             }
10000
10001             index += 2;
10002         }
10003     }
10004
10005     return result;
10006 }
10007
10008 /* [AS] */
10009 void GetOutOfBookInfo( char * buf )
10010 {
10011     int oob[2];
10012     int i;
10013     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10014
10015     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10016     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10017
10018     *buf = '\0';
10019
10020     if( oob[0] >= 0 || oob[1] >= 0 ) {
10021         for( i=0; i<2; i++ ) {
10022             int idx = oob[i];
10023
10024             if( idx >= 0 ) {
10025                 if( i > 0 && oob[0] >= 0 ) {
10026                     strcat( buf, "   " );
10027                 }
10028
10029                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10030                 sprintf( buf+strlen(buf), "%s%.2f", 
10031                     pvInfoList[idx].score >= 0 ? "+" : "",
10032                     pvInfoList[idx].score / 100.0 );
10033             }
10034         }
10035     }
10036 }
10037
10038 /* Save game in PGN style and close the file */
10039 int
10040 SaveGamePGN(f)
10041      FILE *f;
10042 {
10043     int i, offset, linelen, newblock;
10044     time_t tm;
10045 //    char *movetext;
10046     char numtext[32];
10047     int movelen, numlen, blank;
10048     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10049
10050     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10051     
10052     tm = time((time_t *) NULL);
10053     
10054     PrintPGNTags(f, &gameInfo);
10055     
10056     if (backwardMostMove > 0 || startedFromSetupPosition) {
10057         char *fen = PositionToFEN(backwardMostMove, NULL);
10058         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10059         fprintf(f, "\n{--------------\n");
10060         PrintPosition(f, backwardMostMove);
10061         fprintf(f, "--------------}\n");
10062         free(fen);
10063     }
10064     else {
10065         /* [AS] Out of book annotation */
10066         if( appData.saveOutOfBookInfo ) {
10067             char buf[64];
10068
10069             GetOutOfBookInfo( buf );
10070
10071             if( buf[0] != '\0' ) {
10072                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10073             }
10074         }
10075
10076         fprintf(f, "\n");
10077     }
10078
10079     i = backwardMostMove;
10080     linelen = 0;
10081     newblock = TRUE;
10082
10083     while (i < forwardMostMove) {
10084         /* Print comments preceding this move */
10085         if (commentList[i] != NULL) {
10086             if (linelen > 0) fprintf(f, "\n");
10087             fprintf(f, "%s", commentList[i]);
10088             linelen = 0;
10089             newblock = TRUE;
10090         }
10091
10092         /* Format move number */
10093         if ((i % 2) == 0) {
10094             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10095         } else {
10096             if (newblock) {
10097                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10098             } else {
10099                 numtext[0] = NULLCHAR;
10100             }
10101         }
10102         numlen = strlen(numtext);
10103         newblock = FALSE;
10104
10105         /* Print move number */
10106         blank = linelen > 0 && numlen > 0;
10107         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10108             fprintf(f, "\n");
10109             linelen = 0;
10110             blank = 0;
10111         }
10112         if (blank) {
10113             fprintf(f, " ");
10114             linelen++;
10115         }
10116         fprintf(f, "%s", numtext);
10117         linelen += numlen;
10118
10119         /* Get move */
10120         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10121         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10122
10123         /* Print move */
10124         blank = linelen > 0 && movelen > 0;
10125         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10126             fprintf(f, "\n");
10127             linelen = 0;
10128             blank = 0;
10129         }
10130         if (blank) {
10131             fprintf(f, " ");
10132             linelen++;
10133         }
10134         fprintf(f, "%s", move_buffer);
10135         linelen += movelen;
10136
10137         /* [AS] Add PV info if present */
10138         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10139             /* [HGM] add time */
10140             char buf[MSG_SIZ]; int seconds;
10141
10142             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10143
10144             if( seconds <= 0) buf[0] = 0; else
10145             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10146                 seconds = (seconds + 4)/10; // round to full seconds
10147                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10148                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10149             }
10150
10151             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10152                 pvInfoList[i].score >= 0 ? "+" : "",
10153                 pvInfoList[i].score / 100.0,
10154                 pvInfoList[i].depth,
10155                 buf );
10156
10157             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10158
10159             /* Print score/depth */
10160             blank = linelen > 0 && movelen > 0;
10161             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10162                 fprintf(f, "\n");
10163                 linelen = 0;
10164                 blank = 0;
10165             }
10166             if (blank) {
10167                 fprintf(f, " ");
10168                 linelen++;
10169             }
10170             fprintf(f, "%s", move_buffer);
10171             linelen += movelen;
10172         }
10173
10174         i++;
10175     }
10176     
10177     /* Start a new line */
10178     if (linelen > 0) fprintf(f, "\n");
10179
10180     /* Print comments after last move */
10181     if (commentList[i] != NULL) {
10182         fprintf(f, "%s\n", commentList[i]);
10183     }
10184
10185     /* Print result */
10186     if (gameInfo.resultDetails != NULL &&
10187         gameInfo.resultDetails[0] != NULLCHAR) {
10188         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10189                 PGNResult(gameInfo.result));
10190     } else {
10191         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10192     }
10193
10194     fclose(f);
10195     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10196     return TRUE;
10197 }
10198
10199 /* Save game in old style and close the file */
10200 int
10201 SaveGameOldStyle(f)
10202      FILE *f;
10203 {
10204     int i, offset;
10205     time_t tm;
10206     
10207     tm = time((time_t *) NULL);
10208     
10209     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10210     PrintOpponents(f);
10211     
10212     if (backwardMostMove > 0 || startedFromSetupPosition) {
10213         fprintf(f, "\n[--------------\n");
10214         PrintPosition(f, backwardMostMove);
10215         fprintf(f, "--------------]\n");
10216     } else {
10217         fprintf(f, "\n");
10218     }
10219
10220     i = backwardMostMove;
10221     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10222
10223     while (i < forwardMostMove) {
10224         if (commentList[i] != NULL) {
10225             fprintf(f, "[%s]\n", commentList[i]);
10226         }
10227
10228         if ((i % 2) == 1) {
10229             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10230             i++;
10231         } else {
10232             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10233             i++;
10234             if (commentList[i] != NULL) {
10235                 fprintf(f, "\n");
10236                 continue;
10237             }
10238             if (i >= forwardMostMove) {
10239                 fprintf(f, "\n");
10240                 break;
10241             }
10242             fprintf(f, "%s\n", parseList[i]);
10243             i++;
10244         }
10245     }
10246     
10247     if (commentList[i] != NULL) {
10248         fprintf(f, "[%s]\n", commentList[i]);
10249     }
10250
10251     /* This isn't really the old style, but it's close enough */
10252     if (gameInfo.resultDetails != NULL &&
10253         gameInfo.resultDetails[0] != NULLCHAR) {
10254         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10255                 gameInfo.resultDetails);
10256     } else {
10257         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10258     }
10259
10260     fclose(f);
10261     return TRUE;
10262 }
10263
10264 /* Save the current game to open file f and close the file */
10265 int
10266 SaveGame(f, dummy, dummy2)
10267      FILE *f;
10268      int dummy;
10269      char *dummy2;
10270 {
10271     if (gameMode == EditPosition) EditPositionDone(TRUE);
10272     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10273     if (appData.oldSaveStyle)
10274       return SaveGameOldStyle(f);
10275     else
10276       return SaveGamePGN(f);
10277 }
10278
10279 /* Save the current position to the given file */
10280 int
10281 SavePositionToFile(filename)
10282      char *filename;
10283 {
10284     FILE *f;
10285     char buf[MSG_SIZ];
10286
10287     if (strcmp(filename, "-") == 0) {
10288         return SavePosition(stdout, 0, NULL);
10289     } else {
10290         f = fopen(filename, "a");
10291         if (f == NULL) {
10292             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10293             DisplayError(buf, errno);
10294             return FALSE;
10295         } else {
10296             SavePosition(f, 0, NULL);
10297             return TRUE;
10298         }
10299     }
10300 }
10301
10302 /* Save the current position to the given open file and close the file */
10303 int
10304 SavePosition(f, dummy, dummy2)
10305      FILE *f;
10306      int dummy;
10307      char *dummy2;
10308 {
10309     time_t tm;
10310     char *fen;
10311     
10312     if (gameMode == EditPosition) EditPositionDone(TRUE);
10313     if (appData.oldSaveStyle) {
10314         tm = time((time_t *) NULL);
10315     
10316         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10317         PrintOpponents(f);
10318         fprintf(f, "[--------------\n");
10319         PrintPosition(f, currentMove);
10320         fprintf(f, "--------------]\n");
10321     } else {
10322         fen = PositionToFEN(currentMove, NULL);
10323         fprintf(f, "%s\n", fen);
10324         free(fen);
10325     }
10326     fclose(f);
10327     return TRUE;
10328 }
10329
10330 void
10331 ReloadCmailMsgEvent(unregister)
10332      int unregister;
10333 {
10334 #if !WIN32
10335     static char *inFilename = NULL;
10336     static char *outFilename;
10337     int i;
10338     struct stat inbuf, outbuf;
10339     int status;
10340     
10341     /* Any registered moves are unregistered if unregister is set, */
10342     /* i.e. invoked by the signal handler */
10343     if (unregister) {
10344         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10345             cmailMoveRegistered[i] = FALSE;
10346             if (cmailCommentList[i] != NULL) {
10347                 free(cmailCommentList[i]);
10348                 cmailCommentList[i] = NULL;
10349             }
10350         }
10351         nCmailMovesRegistered = 0;
10352     }
10353
10354     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10355         cmailResult[i] = CMAIL_NOT_RESULT;
10356     }
10357     nCmailResults = 0;
10358
10359     if (inFilename == NULL) {
10360         /* Because the filenames are static they only get malloced once  */
10361         /* and they never get freed                                      */
10362         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10363         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10364
10365         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10366         sprintf(outFilename, "%s.out", appData.cmailGameName);
10367     }
10368     
10369     status = stat(outFilename, &outbuf);
10370     if (status < 0) {
10371         cmailMailedMove = FALSE;
10372     } else {
10373         status = stat(inFilename, &inbuf);
10374         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10375     }
10376     
10377     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10378        counts the games, notes how each one terminated, etc.
10379        
10380        It would be nice to remove this kludge and instead gather all
10381        the information while building the game list.  (And to keep it
10382        in the game list nodes instead of having a bunch of fixed-size
10383        parallel arrays.)  Note this will require getting each game's
10384        termination from the PGN tags, as the game list builder does
10385        not process the game moves.  --mann
10386        */
10387     cmailMsgLoaded = TRUE;
10388     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10389     
10390     /* Load first game in the file or popup game menu */
10391     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10392
10393 #endif /* !WIN32 */
10394     return;
10395 }
10396
10397 int
10398 RegisterMove()
10399 {
10400     FILE *f;
10401     char string[MSG_SIZ];
10402
10403     if (   cmailMailedMove
10404         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10405         return TRUE;            /* Allow free viewing  */
10406     }
10407
10408     /* Unregister move to ensure that we don't leave RegisterMove        */
10409     /* with the move registered when the conditions for registering no   */
10410     /* longer hold                                                       */
10411     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10412         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10413         nCmailMovesRegistered --;
10414
10415         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10416           {
10417               free(cmailCommentList[lastLoadGameNumber - 1]);
10418               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10419           }
10420     }
10421
10422     if (cmailOldMove == -1) {
10423         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10424         return FALSE;
10425     }
10426
10427     if (currentMove > cmailOldMove + 1) {
10428         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10429         return FALSE;
10430     }
10431
10432     if (currentMove < cmailOldMove) {
10433         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10434         return FALSE;
10435     }
10436
10437     if (forwardMostMove > currentMove) {
10438         /* Silently truncate extra moves */
10439         TruncateGame();
10440     }
10441
10442     if (   (currentMove == cmailOldMove + 1)
10443         || (   (currentMove == cmailOldMove)
10444             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10445                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10446         if (gameInfo.result != GameUnfinished) {
10447             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10448         }
10449
10450         if (commentList[currentMove] != NULL) {
10451             cmailCommentList[lastLoadGameNumber - 1]
10452               = StrSave(commentList[currentMove]);
10453         }
10454         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10455
10456         if (appData.debugMode)
10457           fprintf(debugFP, "Saving %s for game %d\n",
10458                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10459
10460         sprintf(string,
10461                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10462         
10463         f = fopen(string, "w");
10464         if (appData.oldSaveStyle) {
10465             SaveGameOldStyle(f); /* also closes the file */
10466             
10467             sprintf(string, "%s.pos.out", appData.cmailGameName);
10468             f = fopen(string, "w");
10469             SavePosition(f, 0, NULL); /* also closes the file */
10470         } else {
10471             fprintf(f, "{--------------\n");
10472             PrintPosition(f, currentMove);
10473             fprintf(f, "--------------}\n\n");
10474             
10475             SaveGame(f, 0, NULL); /* also closes the file*/
10476         }
10477         
10478         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10479         nCmailMovesRegistered ++;
10480     } else if (nCmailGames == 1) {
10481         DisplayError(_("You have not made a move yet"), 0);
10482         return FALSE;
10483     }
10484
10485     return TRUE;
10486 }
10487
10488 void
10489 MailMoveEvent()
10490 {
10491 #if !WIN32
10492     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10493     FILE *commandOutput;
10494     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10495     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10496     int nBuffers;
10497     int i;
10498     int archived;
10499     char *arcDir;
10500
10501     if (! cmailMsgLoaded) {
10502         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10503         return;
10504     }
10505
10506     if (nCmailGames == nCmailResults) {
10507         DisplayError(_("No unfinished games"), 0);
10508         return;
10509     }
10510
10511 #if CMAIL_PROHIBIT_REMAIL
10512     if (cmailMailedMove) {
10513         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);
10514         DisplayError(msg, 0);
10515         return;
10516     }
10517 #endif
10518
10519     if (! (cmailMailedMove || RegisterMove())) return;
10520     
10521     if (   cmailMailedMove
10522         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10523         sprintf(string, partCommandString,
10524                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10525         commandOutput = popen(string, "r");
10526
10527         if (commandOutput == NULL) {
10528             DisplayError(_("Failed to invoke cmail"), 0);
10529         } else {
10530             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10531                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10532             }
10533             if (nBuffers > 1) {
10534                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10535                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10536                 nBytes = MSG_SIZ - 1;
10537             } else {
10538                 (void) memcpy(msg, buffer, nBytes);
10539             }
10540             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10541
10542             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10543                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10544
10545                 archived = TRUE;
10546                 for (i = 0; i < nCmailGames; i ++) {
10547                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10548                         archived = FALSE;
10549                     }
10550                 }
10551                 if (   archived
10552                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10553                         != NULL)) {
10554                     sprintf(buffer, "%s/%s.%s.archive",
10555                             arcDir,
10556                             appData.cmailGameName,
10557                             gameInfo.date);
10558                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10559                     cmailMsgLoaded = FALSE;
10560                 }
10561             }
10562
10563             DisplayInformation(msg);
10564             pclose(commandOutput);
10565         }
10566     } else {
10567         if ((*cmailMsg) != '\0') {
10568             DisplayInformation(cmailMsg);
10569         }
10570     }
10571
10572     return;
10573 #endif /* !WIN32 */
10574 }
10575
10576 char *
10577 CmailMsg()
10578 {
10579 #if WIN32
10580     return NULL;
10581 #else
10582     int  prependComma = 0;
10583     char number[5];
10584     char string[MSG_SIZ];       /* Space for game-list */
10585     int  i;
10586     
10587     if (!cmailMsgLoaded) return "";
10588
10589     if (cmailMailedMove) {
10590         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10591     } else {
10592         /* Create a list of games left */
10593         sprintf(string, "[");
10594         for (i = 0; i < nCmailGames; i ++) {
10595             if (! (   cmailMoveRegistered[i]
10596                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10597                 if (prependComma) {
10598                     sprintf(number, ",%d", i + 1);
10599                 } else {
10600                     sprintf(number, "%d", i + 1);
10601                     prependComma = 1;
10602                 }
10603                 
10604                 strcat(string, number);
10605             }
10606         }
10607         strcat(string, "]");
10608
10609         if (nCmailMovesRegistered + nCmailResults == 0) {
10610             switch (nCmailGames) {
10611               case 1:
10612                 sprintf(cmailMsg,
10613                         _("Still need to make move for game\n"));
10614                 break;
10615                 
10616               case 2:
10617                 sprintf(cmailMsg,
10618                         _("Still need to make moves for both games\n"));
10619                 break;
10620                 
10621               default:
10622                 sprintf(cmailMsg,
10623                         _("Still need to make moves for all %d games\n"),
10624                         nCmailGames);
10625                 break;
10626             }
10627         } else {
10628             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10629               case 1:
10630                 sprintf(cmailMsg,
10631                         _("Still need to make a move for game %s\n"),
10632                         string);
10633                 break;
10634                 
10635               case 0:
10636                 if (nCmailResults == nCmailGames) {
10637                     sprintf(cmailMsg, _("No unfinished games\n"));
10638                 } else {
10639                     sprintf(cmailMsg, _("Ready to send mail\n"));
10640                 }
10641                 break;
10642                 
10643               default:
10644                 sprintf(cmailMsg,
10645                         _("Still need to make moves for games %s\n"),
10646                         string);
10647             }
10648         }
10649     }
10650     return cmailMsg;
10651 #endif /* WIN32 */
10652 }
10653
10654 void
10655 ResetGameEvent()
10656 {
10657     if (gameMode == Training)
10658       SetTrainingModeOff();
10659
10660     Reset(TRUE, TRUE);
10661     cmailMsgLoaded = FALSE;
10662     if (appData.icsActive) {
10663       SendToICS(ics_prefix);
10664       SendToICS("refresh\n");
10665     }
10666 }
10667
10668 void
10669 ExitEvent(status)
10670      int status;
10671 {
10672     exiting++;
10673     if (exiting > 2) {
10674       /* Give up on clean exit */
10675       exit(status);
10676     }
10677     if (exiting > 1) {
10678       /* Keep trying for clean exit */
10679       return;
10680     }
10681
10682     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10683
10684     if (telnetISR != NULL) {
10685       RemoveInputSource(telnetISR);
10686     }
10687     if (icsPR != NoProc) {
10688       DestroyChildProcess(icsPR, TRUE);
10689     }
10690
10691     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10692     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10693
10694     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10695     /* make sure this other one finishes before killing it!                  */
10696     if(endingGame) { int count = 0;
10697         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10698         while(endingGame && count++ < 10) DoSleep(1);
10699         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10700     }
10701
10702     /* Kill off chess programs */
10703     if (first.pr != NoProc) {
10704         ExitAnalyzeMode();
10705         
10706         DoSleep( appData.delayBeforeQuit );
10707         SendToProgram("quit\n", &first);
10708         DoSleep( appData.delayAfterQuit );
10709         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10710     }
10711     if (second.pr != NoProc) {
10712         DoSleep( appData.delayBeforeQuit );
10713         SendToProgram("quit\n", &second);
10714         DoSleep( appData.delayAfterQuit );
10715         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10716     }
10717     if (first.isr != NULL) {
10718         RemoveInputSource(first.isr);
10719     }
10720     if (second.isr != NULL) {
10721         RemoveInputSource(second.isr);
10722     }
10723
10724     ShutDownFrontEnd();
10725     exit(status);
10726 }
10727
10728 void
10729 PauseEvent()
10730 {
10731     if (appData.debugMode)
10732         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10733     if (pausing) {
10734         pausing = FALSE;
10735         ModeHighlight();
10736         if (gameMode == MachinePlaysWhite ||
10737             gameMode == MachinePlaysBlack) {
10738             StartClocks();
10739         } else {
10740             DisplayBothClocks();
10741         }
10742         if (gameMode == PlayFromGameFile) {
10743             if (appData.timeDelay >= 0) 
10744                 AutoPlayGameLoop();
10745         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10746             Reset(FALSE, TRUE);
10747             SendToICS(ics_prefix);
10748             SendToICS("refresh\n");
10749         } else if (currentMove < forwardMostMove) {
10750             ForwardInner(forwardMostMove);
10751         }
10752         pauseExamInvalid = FALSE;
10753     } else {
10754         switch (gameMode) {
10755           default:
10756             return;
10757           case IcsExamining:
10758             pauseExamForwardMostMove = forwardMostMove;
10759             pauseExamInvalid = FALSE;
10760             /* fall through */
10761           case IcsObserving:
10762           case IcsPlayingWhite:
10763           case IcsPlayingBlack:
10764             pausing = TRUE;
10765             ModeHighlight();
10766             return;
10767           case PlayFromGameFile:
10768             (void) StopLoadGameTimer();
10769             pausing = TRUE;
10770             ModeHighlight();
10771             break;
10772           case BeginningOfGame:
10773             if (appData.icsActive) return;
10774             /* else fall through */
10775           case MachinePlaysWhite:
10776           case MachinePlaysBlack:
10777           case TwoMachinesPlay:
10778             if (forwardMostMove == 0)
10779               return;           /* don't pause if no one has moved */
10780             if ((gameMode == MachinePlaysWhite &&
10781                  !WhiteOnMove(forwardMostMove)) ||
10782                 (gameMode == MachinePlaysBlack &&
10783                  WhiteOnMove(forwardMostMove))) {
10784                 StopClocks();
10785             }
10786             pausing = TRUE;
10787             ModeHighlight();
10788             break;
10789         }
10790     }
10791 }
10792
10793 void
10794 EditCommentEvent()
10795 {
10796     char title[MSG_SIZ];
10797
10798     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10799         strcpy(title, _("Edit comment"));
10800     } else {
10801         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10802                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10803                 parseList[currentMove - 1]);
10804     }
10805
10806     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10807 }
10808
10809
10810 void
10811 EditTagsEvent()
10812 {
10813     char *tags = PGNTags(&gameInfo);
10814     EditTagsPopUp(tags);
10815     free(tags);
10816 }
10817
10818 void
10819 AnalyzeModeEvent()
10820 {
10821     if (appData.noChessProgram || gameMode == AnalyzeMode)
10822       return;
10823
10824     if (gameMode != AnalyzeFile) {
10825         if (!appData.icsEngineAnalyze) {
10826                EditGameEvent();
10827                if (gameMode != EditGame) return;
10828         }
10829         ResurrectChessProgram();
10830         SendToProgram("analyze\n", &first);
10831         first.analyzing = TRUE;
10832         /*first.maybeThinking = TRUE;*/
10833         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10834         EngineOutputPopUp();
10835     }
10836     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10837     pausing = FALSE;
10838     ModeHighlight();
10839     SetGameInfo();
10840
10841     StartAnalysisClock();
10842     GetTimeMark(&lastNodeCountTime);
10843     lastNodeCount = 0;
10844 }
10845
10846 void
10847 AnalyzeFileEvent()
10848 {
10849     if (appData.noChessProgram || gameMode == AnalyzeFile)
10850       return;
10851
10852     if (gameMode != AnalyzeMode) {
10853         EditGameEvent();
10854         if (gameMode != EditGame) return;
10855         ResurrectChessProgram();
10856         SendToProgram("analyze\n", &first);
10857         first.analyzing = TRUE;
10858         /*first.maybeThinking = TRUE;*/
10859         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10860         EngineOutputPopUp();
10861     }
10862     gameMode = AnalyzeFile;
10863     pausing = FALSE;
10864     ModeHighlight();
10865     SetGameInfo();
10866
10867     StartAnalysisClock();
10868     GetTimeMark(&lastNodeCountTime);
10869     lastNodeCount = 0;
10870 }
10871
10872 void
10873 MachineWhiteEvent()
10874 {
10875     char buf[MSG_SIZ];
10876     char *bookHit = NULL;
10877
10878     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10879       return;
10880
10881
10882     if (gameMode == PlayFromGameFile || 
10883         gameMode == TwoMachinesPlay  || 
10884         gameMode == Training         || 
10885         gameMode == AnalyzeMode      || 
10886         gameMode == EndOfGame)
10887         EditGameEvent();
10888
10889     if (gameMode == EditPosition) 
10890         EditPositionDone(TRUE);
10891
10892     if (!WhiteOnMove(currentMove)) {
10893         DisplayError(_("It is not White's turn"), 0);
10894         return;
10895     }
10896   
10897     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10898       ExitAnalyzeMode();
10899
10900     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10901         gameMode == AnalyzeFile)
10902         TruncateGame();
10903
10904     ResurrectChessProgram();    /* in case it isn't running */
10905     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10906         gameMode = MachinePlaysWhite;
10907         ResetClocks();
10908     } else
10909     gameMode = MachinePlaysWhite;
10910     pausing = FALSE;
10911     ModeHighlight();
10912     SetGameInfo();
10913     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10914     DisplayTitle(buf);
10915     if (first.sendName) {
10916       sprintf(buf, "name %s\n", gameInfo.black);
10917       SendToProgram(buf, &first);
10918     }
10919     if (first.sendTime) {
10920       if (first.useColors) {
10921         SendToProgram("black\n", &first); /*gnu kludge*/
10922       }
10923       SendTimeRemaining(&first, TRUE);
10924     }
10925     if (first.useColors) {
10926       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10927     }
10928     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10929     SetMachineThinkingEnables();
10930     first.maybeThinking = TRUE;
10931     StartClocks();
10932     firstMove = FALSE;
10933
10934     if (appData.autoFlipView && !flipView) {
10935       flipView = !flipView;
10936       DrawPosition(FALSE, NULL);
10937       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10938     }
10939
10940     if(bookHit) { // [HGM] book: simulate book reply
10941         static char bookMove[MSG_SIZ]; // a bit generous?
10942
10943         programStats.nodes = programStats.depth = programStats.time = 
10944         programStats.score = programStats.got_only_move = 0;
10945         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10946
10947         strcpy(bookMove, "move ");
10948         strcat(bookMove, bookHit);
10949         HandleMachineMove(bookMove, &first);
10950     }
10951 }
10952
10953 void
10954 MachineBlackEvent()
10955 {
10956     char buf[MSG_SIZ];
10957    char *bookHit = NULL;
10958
10959     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10960         return;
10961
10962
10963     if (gameMode == PlayFromGameFile || 
10964         gameMode == TwoMachinesPlay  || 
10965         gameMode == Training         || 
10966         gameMode == AnalyzeMode      || 
10967         gameMode == EndOfGame)
10968         EditGameEvent();
10969
10970     if (gameMode == EditPosition) 
10971         EditPositionDone(TRUE);
10972
10973     if (WhiteOnMove(currentMove)) {
10974         DisplayError(_("It is not Black's turn"), 0);
10975         return;
10976     }
10977     
10978     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10979       ExitAnalyzeMode();
10980
10981     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10982         gameMode == AnalyzeFile)
10983         TruncateGame();
10984
10985     ResurrectChessProgram();    /* in case it isn't running */
10986     gameMode = MachinePlaysBlack;
10987     pausing = FALSE;
10988     ModeHighlight();
10989     SetGameInfo();
10990     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10991     DisplayTitle(buf);
10992     if (first.sendName) {
10993       sprintf(buf, "name %s\n", gameInfo.white);
10994       SendToProgram(buf, &first);
10995     }
10996     if (first.sendTime) {
10997       if (first.useColors) {
10998         SendToProgram("white\n", &first); /*gnu kludge*/
10999       }
11000       SendTimeRemaining(&first, FALSE);
11001     }
11002     if (first.useColors) {
11003       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11004     }
11005     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11006     SetMachineThinkingEnables();
11007     first.maybeThinking = TRUE;
11008     StartClocks();
11009
11010     if (appData.autoFlipView && flipView) {
11011       flipView = !flipView;
11012       DrawPosition(FALSE, NULL);
11013       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11014     }
11015     if(bookHit) { // [HGM] book: simulate book reply
11016         static char bookMove[MSG_SIZ]; // a bit generous?
11017
11018         programStats.nodes = programStats.depth = programStats.time = 
11019         programStats.score = programStats.got_only_move = 0;
11020         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11021
11022         strcpy(bookMove, "move ");
11023         strcat(bookMove, bookHit);
11024         HandleMachineMove(bookMove, &first);
11025     }
11026 }
11027
11028
11029 void
11030 DisplayTwoMachinesTitle()
11031 {
11032     char buf[MSG_SIZ];
11033     if (appData.matchGames > 0) {
11034         if (first.twoMachinesColor[0] == 'w') {
11035             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11036                     gameInfo.white, gameInfo.black,
11037                     first.matchWins, second.matchWins,
11038                     matchGame - 1 - (first.matchWins + second.matchWins));
11039         } else {
11040             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11041                     gameInfo.white, gameInfo.black,
11042                     second.matchWins, first.matchWins,
11043                     matchGame - 1 - (first.matchWins + second.matchWins));
11044         }
11045     } else {
11046         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11047     }
11048     DisplayTitle(buf);
11049 }
11050
11051 void
11052 TwoMachinesEvent P((void))
11053 {
11054     int i;
11055     char buf[MSG_SIZ];
11056     ChessProgramState *onmove;
11057     char *bookHit = NULL;
11058     
11059     if (appData.noChessProgram) return;
11060
11061     switch (gameMode) {
11062       case TwoMachinesPlay:
11063         return;
11064       case MachinePlaysWhite:
11065       case MachinePlaysBlack:
11066         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11067             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11068             return;
11069         }
11070         /* fall through */
11071       case BeginningOfGame:
11072       case PlayFromGameFile:
11073       case EndOfGame:
11074         EditGameEvent();
11075         if (gameMode != EditGame) return;
11076         break;
11077       case EditPosition:
11078         EditPositionDone(TRUE);
11079         break;
11080       case AnalyzeMode:
11081       case AnalyzeFile:
11082         ExitAnalyzeMode();
11083         break;
11084       case EditGame:
11085       default:
11086         break;
11087     }
11088
11089 //    forwardMostMove = currentMove;
11090     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11091     ResurrectChessProgram();    /* in case first program isn't running */
11092
11093     if (second.pr == NULL) {
11094         StartChessProgram(&second);
11095         if (second.protocolVersion == 1) {
11096           TwoMachinesEventIfReady();
11097         } else {
11098           /* kludge: allow timeout for initial "feature" command */
11099           FreezeUI();
11100           DisplayMessage("", _("Starting second chess program"));
11101           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11102         }
11103         return;
11104     }
11105     DisplayMessage("", "");
11106     InitChessProgram(&second, FALSE);
11107     SendToProgram("force\n", &second);
11108     if (startedFromSetupPosition) {
11109         SendBoard(&second, backwardMostMove);
11110     if (appData.debugMode) {
11111         fprintf(debugFP, "Two Machines\n");
11112     }
11113     }
11114     for (i = backwardMostMove; i < forwardMostMove; i++) {
11115         SendMoveToProgram(i, &second);
11116     }
11117
11118     gameMode = TwoMachinesPlay;
11119     pausing = FALSE;
11120     ModeHighlight();
11121     SetGameInfo();
11122     DisplayTwoMachinesTitle();
11123     firstMove = TRUE;
11124     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11125         onmove = &first;
11126     } else {
11127         onmove = &second;
11128     }
11129
11130     SendToProgram(first.computerString, &first);
11131     if (first.sendName) {
11132       sprintf(buf, "name %s\n", second.tidy);
11133       SendToProgram(buf, &first);
11134     }
11135     SendToProgram(second.computerString, &second);
11136     if (second.sendName) {
11137       sprintf(buf, "name %s\n", first.tidy);
11138       SendToProgram(buf, &second);
11139     }
11140
11141     ResetClocks();
11142     if (!first.sendTime || !second.sendTime) {
11143         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11144         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11145     }
11146     if (onmove->sendTime) {
11147       if (onmove->useColors) {
11148         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11149       }
11150       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11151     }
11152     if (onmove->useColors) {
11153       SendToProgram(onmove->twoMachinesColor, onmove);
11154     }
11155     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11156 //    SendToProgram("go\n", onmove);
11157     onmove->maybeThinking = TRUE;
11158     SetMachineThinkingEnables();
11159
11160     StartClocks();
11161
11162     if(bookHit) { // [HGM] book: simulate book reply
11163         static char bookMove[MSG_SIZ]; // a bit generous?
11164
11165         programStats.nodes = programStats.depth = programStats.time = 
11166         programStats.score = programStats.got_only_move = 0;
11167         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11168
11169         strcpy(bookMove, "move ");
11170         strcat(bookMove, bookHit);
11171         savedMessage = bookMove; // args for deferred call
11172         savedState = onmove;
11173         ScheduleDelayedEvent(DeferredBookMove, 1);
11174     }
11175 }
11176
11177 void
11178 TrainingEvent()
11179 {
11180     if (gameMode == Training) {
11181       SetTrainingModeOff();
11182       gameMode = PlayFromGameFile;
11183       DisplayMessage("", _("Training mode off"));
11184     } else {
11185       gameMode = Training;
11186       animateTraining = appData.animate;
11187
11188       /* make sure we are not already at the end of the game */
11189       if (currentMove < forwardMostMove) {
11190         SetTrainingModeOn();
11191         DisplayMessage("", _("Training mode on"));
11192       } else {
11193         gameMode = PlayFromGameFile;
11194         DisplayError(_("Already at end of game"), 0);
11195       }
11196     }
11197     ModeHighlight();
11198 }
11199
11200 void
11201 IcsClientEvent()
11202 {
11203     if (!appData.icsActive) return;
11204     switch (gameMode) {
11205       case IcsPlayingWhite:
11206       case IcsPlayingBlack:
11207       case IcsObserving:
11208       case IcsIdle:
11209       case BeginningOfGame:
11210       case IcsExamining:
11211         return;
11212
11213       case EditGame:
11214         break;
11215
11216       case EditPosition:
11217         EditPositionDone(TRUE);
11218         break;
11219
11220       case AnalyzeMode:
11221       case AnalyzeFile:
11222         ExitAnalyzeMode();
11223         break;
11224         
11225       default:
11226         EditGameEvent();
11227         break;
11228     }
11229
11230     gameMode = IcsIdle;
11231     ModeHighlight();
11232     return;
11233 }
11234
11235
11236 void
11237 EditGameEvent()
11238 {
11239     int i;
11240
11241     switch (gameMode) {
11242       case Training:
11243         SetTrainingModeOff();
11244         break;
11245       case MachinePlaysWhite:
11246       case MachinePlaysBlack:
11247       case BeginningOfGame:
11248         SendToProgram("force\n", &first);
11249         SetUserThinkingEnables();
11250         break;
11251       case PlayFromGameFile:
11252         (void) StopLoadGameTimer();
11253         if (gameFileFP != NULL) {
11254             gameFileFP = NULL;
11255         }
11256         break;
11257       case EditPosition:
11258         EditPositionDone(TRUE);
11259         break;
11260       case AnalyzeMode:
11261       case AnalyzeFile:
11262         ExitAnalyzeMode();
11263         SendToProgram("force\n", &first);
11264         break;
11265       case TwoMachinesPlay:
11266         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11267         ResurrectChessProgram();
11268         SetUserThinkingEnables();
11269         break;
11270       case EndOfGame:
11271         ResurrectChessProgram();
11272         break;
11273       case IcsPlayingBlack:
11274       case IcsPlayingWhite:
11275         DisplayError(_("Warning: You are still playing a game"), 0);
11276         break;
11277       case IcsObserving:
11278         DisplayError(_("Warning: You are still observing a game"), 0);
11279         break;
11280       case IcsExamining:
11281         DisplayError(_("Warning: You are still examining a game"), 0);
11282         break;
11283       case IcsIdle:
11284         break;
11285       case EditGame:
11286       default:
11287         return;
11288     }
11289     
11290     pausing = FALSE;
11291     StopClocks();
11292     first.offeredDraw = second.offeredDraw = 0;
11293
11294     if (gameMode == PlayFromGameFile) {
11295         whiteTimeRemaining = timeRemaining[0][currentMove];
11296         blackTimeRemaining = timeRemaining[1][currentMove];
11297         DisplayTitle("");
11298     }
11299
11300     if (gameMode == MachinePlaysWhite ||
11301         gameMode == MachinePlaysBlack ||
11302         gameMode == TwoMachinesPlay ||
11303         gameMode == EndOfGame) {
11304         i = forwardMostMove;
11305         while (i > currentMove) {
11306             SendToProgram("undo\n", &first);
11307             i--;
11308         }
11309         whiteTimeRemaining = timeRemaining[0][currentMove];
11310         blackTimeRemaining = timeRemaining[1][currentMove];
11311         DisplayBothClocks();
11312         if (whiteFlag || blackFlag) {
11313             whiteFlag = blackFlag = 0;
11314         }
11315         DisplayTitle("");
11316     }           
11317     
11318     gameMode = EditGame;
11319     ModeHighlight();
11320     SetGameInfo();
11321 }
11322
11323
11324 void
11325 EditPositionEvent()
11326 {
11327     if (gameMode == EditPosition) {
11328         EditGameEvent();
11329         return;
11330     }
11331     
11332     EditGameEvent();
11333     if (gameMode != EditGame) return;
11334     
11335     gameMode = EditPosition;
11336     ModeHighlight();
11337     SetGameInfo();
11338     if (currentMove > 0)
11339       CopyBoard(boards[0], boards[currentMove]);
11340     
11341     blackPlaysFirst = !WhiteOnMove(currentMove);
11342     ResetClocks();
11343     currentMove = forwardMostMove = backwardMostMove = 0;
11344     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11345     DisplayMove(-1);
11346 }
11347
11348 void
11349 ExitAnalyzeMode()
11350 {
11351     /* [DM] icsEngineAnalyze - possible call from other functions */
11352     if (appData.icsEngineAnalyze) {
11353         appData.icsEngineAnalyze = FALSE;
11354
11355         DisplayMessage("",_("Close ICS engine analyze..."));
11356     }
11357     if (first.analysisSupport && first.analyzing) {
11358       SendToProgram("exit\n", &first);
11359       first.analyzing = FALSE;
11360     }
11361     thinkOutput[0] = NULLCHAR;
11362 }
11363
11364 void
11365 EditPositionDone(Boolean fakeRights)
11366 {
11367     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11368
11369     startedFromSetupPosition = TRUE;
11370     InitChessProgram(&first, FALSE);
11371     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11372       boards[0][EP_STATUS] = EP_NONE;
11373       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11374     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11375         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11376         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11377       } else boards[0][CASTLING][2] = NoRights;
11378     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11379         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11380         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11381       } else boards[0][CASTLING][5] = NoRights;
11382     }
11383     SendToProgram("force\n", &first);
11384     if (blackPlaysFirst) {
11385         strcpy(moveList[0], "");
11386         strcpy(parseList[0], "");
11387         currentMove = forwardMostMove = backwardMostMove = 1;
11388         CopyBoard(boards[1], boards[0]);
11389     } else {
11390         currentMove = forwardMostMove = backwardMostMove = 0;
11391     }
11392     SendBoard(&first, forwardMostMove);
11393     if (appData.debugMode) {
11394         fprintf(debugFP, "EditPosDone\n");
11395     }
11396     DisplayTitle("");
11397     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11398     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11399     gameMode = EditGame;
11400     ModeHighlight();
11401     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11402     ClearHighlights(); /* [AS] */
11403 }
11404
11405 /* Pause for `ms' milliseconds */
11406 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11407 void
11408 TimeDelay(ms)
11409      long ms;
11410 {
11411     TimeMark m1, m2;
11412
11413     GetTimeMark(&m1);
11414     do {
11415         GetTimeMark(&m2);
11416     } while (SubtractTimeMarks(&m2, &m1) < ms);
11417 }
11418
11419 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11420 void
11421 SendMultiLineToICS(buf)
11422      char *buf;
11423 {
11424     char temp[MSG_SIZ+1], *p;
11425     int len;
11426
11427     len = strlen(buf);
11428     if (len > MSG_SIZ)
11429       len = MSG_SIZ;
11430   
11431     strncpy(temp, buf, len);
11432     temp[len] = 0;
11433
11434     p = temp;
11435     while (*p) {
11436         if (*p == '\n' || *p == '\r')
11437           *p = ' ';
11438         ++p;
11439     }
11440
11441     strcat(temp, "\n");
11442     SendToICS(temp);
11443     SendToPlayer(temp, strlen(temp));
11444 }
11445
11446 void
11447 SetWhiteToPlayEvent()
11448 {
11449     if (gameMode == EditPosition) {
11450         blackPlaysFirst = FALSE;
11451         DisplayBothClocks();    /* works because currentMove is 0 */
11452     } else if (gameMode == IcsExamining) {
11453         SendToICS(ics_prefix);
11454         SendToICS("tomove white\n");
11455     }
11456 }
11457
11458 void
11459 SetBlackToPlayEvent()
11460 {
11461     if (gameMode == EditPosition) {
11462         blackPlaysFirst = TRUE;
11463         currentMove = 1;        /* kludge */
11464         DisplayBothClocks();
11465         currentMove = 0;
11466     } else if (gameMode == IcsExamining) {
11467         SendToICS(ics_prefix);
11468         SendToICS("tomove black\n");
11469     }
11470 }
11471
11472 void
11473 EditPositionMenuEvent(selection, x, y)
11474      ChessSquare selection;
11475      int x, y;
11476 {
11477     char buf[MSG_SIZ];
11478     ChessSquare piece = boards[0][y][x];
11479
11480     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11481
11482     switch (selection) {
11483       case ClearBoard:
11484         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11485             SendToICS(ics_prefix);
11486             SendToICS("bsetup clear\n");
11487         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11488             SendToICS(ics_prefix);
11489             SendToICS("clearboard\n");
11490         } else {
11491             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11492                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11493                 for (y = 0; y < BOARD_HEIGHT; y++) {
11494                     if (gameMode == IcsExamining) {
11495                         if (boards[currentMove][y][x] != EmptySquare) {
11496                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11497                                     AAA + x, ONE + y);
11498                             SendToICS(buf);
11499                         }
11500                     } else {
11501                         boards[0][y][x] = p;
11502                     }
11503                 }
11504             }
11505         }
11506         if (gameMode == EditPosition) {
11507             DrawPosition(FALSE, boards[0]);
11508         }
11509         break;
11510
11511       case WhitePlay:
11512         SetWhiteToPlayEvent();
11513         break;
11514
11515       case BlackPlay:
11516         SetBlackToPlayEvent();
11517         break;
11518
11519       case EmptySquare:
11520         if (gameMode == IcsExamining) {
11521             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11522             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11523             SendToICS(buf);
11524         } else {
11525             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11526                 if(x == BOARD_LEFT-2) {
11527                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11528                     boards[0][y][1] = 0;
11529                 } else
11530                 if(x == BOARD_RGHT+1) {
11531                     if(y >= gameInfo.holdingsSize) break;
11532                     boards[0][y][BOARD_WIDTH-2] = 0;
11533                 } else break;
11534             }
11535             boards[0][y][x] = EmptySquare;
11536             DrawPosition(FALSE, boards[0]);
11537         }
11538         break;
11539
11540       case PromotePiece:
11541         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11542            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11543             selection = (ChessSquare) (PROMOTED piece);
11544         } else if(piece == EmptySquare) selection = WhiteSilver;
11545         else selection = (ChessSquare)((int)piece - 1);
11546         goto defaultlabel;
11547
11548       case DemotePiece:
11549         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11550            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11551             selection = (ChessSquare) (DEMOTED piece);
11552         } else if(piece == EmptySquare) selection = BlackSilver;
11553         else selection = (ChessSquare)((int)piece + 1);       
11554         goto defaultlabel;
11555
11556       case WhiteQueen:
11557       case BlackQueen:
11558         if(gameInfo.variant == VariantShatranj ||
11559            gameInfo.variant == VariantXiangqi  ||
11560            gameInfo.variant == VariantCourier  ||
11561            gameInfo.variant == VariantMakruk     )
11562             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11563         goto defaultlabel;
11564
11565       case WhiteKing:
11566       case BlackKing:
11567         if(gameInfo.variant == VariantXiangqi)
11568             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11569         if(gameInfo.variant == VariantKnightmate)
11570             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11571       default:
11572         defaultlabel:
11573         if (gameMode == IcsExamining) {
11574             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11575             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11576                     PieceToChar(selection), AAA + x, ONE + y);
11577             SendToICS(buf);
11578         } else {
11579             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11580                 int n;
11581                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11582                     n = PieceToNumber(selection - BlackPawn);
11583                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11584                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11585                     boards[0][BOARD_HEIGHT-1-n][1]++;
11586                 } else
11587                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11588                     n = PieceToNumber(selection);
11589                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11590                     boards[0][n][BOARD_WIDTH-1] = selection;
11591                     boards[0][n][BOARD_WIDTH-2]++;
11592                 }
11593             } else
11594             boards[0][y][x] = selection;
11595             DrawPosition(TRUE, boards[0]);
11596         }
11597         break;
11598     }
11599 }
11600
11601
11602 void
11603 DropMenuEvent(selection, x, y)
11604      ChessSquare selection;
11605      int x, y;
11606 {
11607     ChessMove moveType;
11608
11609     switch (gameMode) {
11610       case IcsPlayingWhite:
11611       case MachinePlaysBlack:
11612         if (!WhiteOnMove(currentMove)) {
11613             DisplayMoveError(_("It is Black's turn"));
11614             return;
11615         }
11616         moveType = WhiteDrop;
11617         break;
11618       case IcsPlayingBlack:
11619       case MachinePlaysWhite:
11620         if (WhiteOnMove(currentMove)) {
11621             DisplayMoveError(_("It is White's turn"));
11622             return;
11623         }
11624         moveType = BlackDrop;
11625         break;
11626       case EditGame:
11627         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11628         break;
11629       default:
11630         return;
11631     }
11632
11633     if (moveType == BlackDrop && selection < BlackPawn) {
11634       selection = (ChessSquare) ((int) selection
11635                                  + (int) BlackPawn - (int) WhitePawn);
11636     }
11637     if (boards[currentMove][y][x] != EmptySquare) {
11638         DisplayMoveError(_("That square is occupied"));
11639         return;
11640     }
11641
11642     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11643 }
11644
11645 void
11646 AcceptEvent()
11647 {
11648     /* Accept a pending offer of any kind from opponent */
11649     
11650     if (appData.icsActive) {
11651         SendToICS(ics_prefix);
11652         SendToICS("accept\n");
11653     } else if (cmailMsgLoaded) {
11654         if (currentMove == cmailOldMove &&
11655             commentList[cmailOldMove] != NULL &&
11656             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11657                    "Black offers a draw" : "White offers a draw")) {
11658             TruncateGame();
11659             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11660             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11661         } else {
11662             DisplayError(_("There is no pending offer on this move"), 0);
11663             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11664         }
11665     } else {
11666         /* Not used for offers from chess program */
11667     }
11668 }
11669
11670 void
11671 DeclineEvent()
11672 {
11673     /* Decline a pending offer of any kind from opponent */
11674     
11675     if (appData.icsActive) {
11676         SendToICS(ics_prefix);
11677         SendToICS("decline\n");
11678     } else if (cmailMsgLoaded) {
11679         if (currentMove == cmailOldMove &&
11680             commentList[cmailOldMove] != NULL &&
11681             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11682                    "Black offers a draw" : "White offers a draw")) {
11683 #ifdef NOTDEF
11684             AppendComment(cmailOldMove, "Draw declined", TRUE);
11685             DisplayComment(cmailOldMove - 1, "Draw declined");
11686 #endif /*NOTDEF*/
11687         } else {
11688             DisplayError(_("There is no pending offer on this move"), 0);
11689         }
11690     } else {
11691         /* Not used for offers from chess program */
11692     }
11693 }
11694
11695 void
11696 RematchEvent()
11697 {
11698     /* Issue ICS rematch command */
11699     if (appData.icsActive) {
11700         SendToICS(ics_prefix);
11701         SendToICS("rematch\n");
11702     }
11703 }
11704
11705 void
11706 CallFlagEvent()
11707 {
11708     /* Call your opponent's flag (claim a win on time) */
11709     if (appData.icsActive) {
11710         SendToICS(ics_prefix);
11711         SendToICS("flag\n");
11712     } else {
11713         switch (gameMode) {
11714           default:
11715             return;
11716           case MachinePlaysWhite:
11717             if (whiteFlag) {
11718                 if (blackFlag)
11719                   GameEnds(GameIsDrawn, "Both players ran out of time",
11720                            GE_PLAYER);
11721                 else
11722                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11723             } else {
11724                 DisplayError(_("Your opponent is not out of time"), 0);
11725             }
11726             break;
11727           case MachinePlaysBlack:
11728             if (blackFlag) {
11729                 if (whiteFlag)
11730                   GameEnds(GameIsDrawn, "Both players ran out of time",
11731                            GE_PLAYER);
11732                 else
11733                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11734             } else {
11735                 DisplayError(_("Your opponent is not out of time"), 0);
11736             }
11737             break;
11738         }
11739     }
11740 }
11741
11742 void
11743 DrawEvent()
11744 {
11745     /* Offer draw or accept pending draw offer from opponent */
11746     
11747     if (appData.icsActive) {
11748         /* Note: tournament rules require draw offers to be
11749            made after you make your move but before you punch
11750            your clock.  Currently ICS doesn't let you do that;
11751            instead, you immediately punch your clock after making
11752            a move, but you can offer a draw at any time. */
11753         
11754         SendToICS(ics_prefix);
11755         SendToICS("draw\n");
11756     } else if (cmailMsgLoaded) {
11757         if (currentMove == cmailOldMove &&
11758             commentList[cmailOldMove] != NULL &&
11759             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11760                    "Black offers a draw" : "White offers a draw")) {
11761             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11762             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11763         } else if (currentMove == cmailOldMove + 1) {
11764             char *offer = WhiteOnMove(cmailOldMove) ?
11765               "White offers a draw" : "Black offers a draw";
11766             AppendComment(currentMove, offer, TRUE);
11767             DisplayComment(currentMove - 1, offer);
11768             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11769         } else {
11770             DisplayError(_("You must make your move before offering a draw"), 0);
11771             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11772         }
11773     } else if (first.offeredDraw) {
11774         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11775     } else {
11776         if (first.sendDrawOffers) {
11777             SendToProgram("draw\n", &first);
11778             userOfferedDraw = TRUE;
11779         }
11780     }
11781 }
11782
11783 void
11784 AdjournEvent()
11785 {
11786     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11787     
11788     if (appData.icsActive) {
11789         SendToICS(ics_prefix);
11790         SendToICS("adjourn\n");
11791     } else {
11792         /* Currently GNU Chess doesn't offer or accept Adjourns */
11793     }
11794 }
11795
11796
11797 void
11798 AbortEvent()
11799 {
11800     /* Offer Abort or accept pending Abort offer from opponent */
11801     
11802     if (appData.icsActive) {
11803         SendToICS(ics_prefix);
11804         SendToICS("abort\n");
11805     } else {
11806         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11807     }
11808 }
11809
11810 void
11811 ResignEvent()
11812 {
11813     /* Resign.  You can do this even if it's not your turn. */
11814     
11815     if (appData.icsActive) {
11816         SendToICS(ics_prefix);
11817         SendToICS("resign\n");
11818     } else {
11819         switch (gameMode) {
11820           case MachinePlaysWhite:
11821             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11822             break;
11823           case MachinePlaysBlack:
11824             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11825             break;
11826           case EditGame:
11827             if (cmailMsgLoaded) {
11828                 TruncateGame();
11829                 if (WhiteOnMove(cmailOldMove)) {
11830                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11831                 } else {
11832                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11833                 }
11834                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11835             }
11836             break;
11837           default:
11838             break;
11839         }
11840     }
11841 }
11842
11843
11844 void
11845 StopObservingEvent()
11846 {
11847     /* Stop observing current games */
11848     SendToICS(ics_prefix);
11849     SendToICS("unobserve\n");
11850 }
11851
11852 void
11853 StopExaminingEvent()
11854 {
11855     /* Stop observing current game */
11856     SendToICS(ics_prefix);
11857     SendToICS("unexamine\n");
11858 }
11859
11860 void
11861 ForwardInner(target)
11862      int target;
11863 {
11864     int limit;
11865
11866     if (appData.debugMode)
11867         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11868                 target, currentMove, forwardMostMove);
11869
11870     if (gameMode == EditPosition)
11871       return;
11872
11873     if (gameMode == PlayFromGameFile && !pausing)
11874       PauseEvent();
11875     
11876     if (gameMode == IcsExamining && pausing)
11877       limit = pauseExamForwardMostMove;
11878     else
11879       limit = forwardMostMove;
11880     
11881     if (target > limit) target = limit;
11882
11883     if (target > 0 && moveList[target - 1][0]) {
11884         int fromX, fromY, toX, toY;
11885         toX = moveList[target - 1][2] - AAA;
11886         toY = moveList[target - 1][3] - ONE;
11887         if (moveList[target - 1][1] == '@') {
11888             if (appData.highlightLastMove) {
11889                 SetHighlights(-1, -1, toX, toY);
11890             }
11891         } else {
11892             fromX = moveList[target - 1][0] - AAA;
11893             fromY = moveList[target - 1][1] - ONE;
11894             if (target == currentMove + 1) {
11895                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11896             }
11897             if (appData.highlightLastMove) {
11898                 SetHighlights(fromX, fromY, toX, toY);
11899             }
11900         }
11901     }
11902     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11903         gameMode == Training || gameMode == PlayFromGameFile || 
11904         gameMode == AnalyzeFile) {
11905         while (currentMove < target) {
11906             SendMoveToProgram(currentMove++, &first);
11907         }
11908     } else {
11909         currentMove = target;
11910     }
11911     
11912     if (gameMode == EditGame || gameMode == EndOfGame) {
11913         whiteTimeRemaining = timeRemaining[0][currentMove];
11914         blackTimeRemaining = timeRemaining[1][currentMove];
11915     }
11916     DisplayBothClocks();
11917     DisplayMove(currentMove - 1);
11918     DrawPosition(FALSE, boards[currentMove]);
11919     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11920     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11921         DisplayComment(currentMove - 1, commentList[currentMove]);
11922     }
11923 }
11924
11925
11926 void
11927 ForwardEvent()
11928 {
11929     if (gameMode == IcsExamining && !pausing) {
11930         SendToICS(ics_prefix);
11931         SendToICS("forward\n");
11932     } else {
11933         ForwardInner(currentMove + 1);
11934     }
11935 }
11936
11937 void
11938 ToEndEvent()
11939 {
11940     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11941         /* to optimze, we temporarily turn off analysis mode while we feed
11942          * the remaining moves to the engine. Otherwise we get analysis output
11943          * after each move.
11944          */ 
11945         if (first.analysisSupport) {
11946           SendToProgram("exit\nforce\n", &first);
11947           first.analyzing = FALSE;
11948         }
11949     }
11950         
11951     if (gameMode == IcsExamining && !pausing) {
11952         SendToICS(ics_prefix);
11953         SendToICS("forward 999999\n");
11954     } else {
11955         ForwardInner(forwardMostMove);
11956     }
11957
11958     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11959         /* we have fed all the moves, so reactivate analysis mode */
11960         SendToProgram("analyze\n", &first);
11961         first.analyzing = TRUE;
11962         /*first.maybeThinking = TRUE;*/
11963         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11964     }
11965 }
11966
11967 void
11968 BackwardInner(target)
11969      int target;
11970 {
11971     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11972
11973     if (appData.debugMode)
11974         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11975                 target, currentMove, forwardMostMove);
11976
11977     if (gameMode == EditPosition) return;
11978     if (currentMove <= backwardMostMove) {
11979         ClearHighlights();
11980         DrawPosition(full_redraw, boards[currentMove]);
11981         return;
11982     }
11983     if (gameMode == PlayFromGameFile && !pausing)
11984       PauseEvent();
11985     
11986     if (moveList[target][0]) {
11987         int fromX, fromY, toX, toY;
11988         toX = moveList[target][2] - AAA;
11989         toY = moveList[target][3] - ONE;
11990         if (moveList[target][1] == '@') {
11991             if (appData.highlightLastMove) {
11992                 SetHighlights(-1, -1, toX, toY);
11993             }
11994         } else {
11995             fromX = moveList[target][0] - AAA;
11996             fromY = moveList[target][1] - ONE;
11997             if (target == currentMove - 1) {
11998                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11999             }
12000             if (appData.highlightLastMove) {
12001                 SetHighlights(fromX, fromY, toX, toY);
12002             }
12003         }
12004     }
12005     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12006         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12007         while (currentMove > target) {
12008             SendToProgram("undo\n", &first);
12009             currentMove--;
12010         }
12011     } else {
12012         currentMove = target;
12013     }
12014     
12015     if (gameMode == EditGame || gameMode == EndOfGame) {
12016         whiteTimeRemaining = timeRemaining[0][currentMove];
12017         blackTimeRemaining = timeRemaining[1][currentMove];
12018     }
12019     DisplayBothClocks();
12020     DisplayMove(currentMove - 1);
12021     DrawPosition(full_redraw, boards[currentMove]);
12022     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12023     // [HGM] PV info: routine tests if comment empty
12024     DisplayComment(currentMove - 1, commentList[currentMove]);
12025 }
12026
12027 void
12028 BackwardEvent()
12029 {
12030     if (gameMode == IcsExamining && !pausing) {
12031         SendToICS(ics_prefix);
12032         SendToICS("backward\n");
12033     } else {
12034         BackwardInner(currentMove - 1);
12035     }
12036 }
12037
12038 void
12039 ToStartEvent()
12040 {
12041     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12042         /* to optimize, we temporarily turn off analysis mode while we undo
12043          * all the moves. Otherwise we get analysis output after each undo.
12044          */ 
12045         if (first.analysisSupport) {
12046           SendToProgram("exit\nforce\n", &first);
12047           first.analyzing = FALSE;
12048         }
12049     }
12050
12051     if (gameMode == IcsExamining && !pausing) {
12052         SendToICS(ics_prefix);
12053         SendToICS("backward 999999\n");
12054     } else {
12055         BackwardInner(backwardMostMove);
12056     }
12057
12058     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12059         /* we have fed all the moves, so reactivate analysis mode */
12060         SendToProgram("analyze\n", &first);
12061         first.analyzing = TRUE;
12062         /*first.maybeThinking = TRUE;*/
12063         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12064     }
12065 }
12066
12067 void
12068 ToNrEvent(int to)
12069 {
12070   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12071   if (to >= forwardMostMove) to = forwardMostMove;
12072   if (to <= backwardMostMove) to = backwardMostMove;
12073   if (to < currentMove) {
12074     BackwardInner(to);
12075   } else {
12076     ForwardInner(to);
12077   }
12078 }
12079
12080 void
12081 RevertEvent()
12082 {
12083     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12084         return;
12085     }
12086     if (gameMode != IcsExamining) {
12087         DisplayError(_("You are not examining a game"), 0);
12088         return;
12089     }
12090     if (pausing) {
12091         DisplayError(_("You can't revert while pausing"), 0);
12092         return;
12093     }
12094     SendToICS(ics_prefix);
12095     SendToICS("revert\n");
12096 }
12097
12098 void
12099 RetractMoveEvent()
12100 {
12101     switch (gameMode) {
12102       case MachinePlaysWhite:
12103       case MachinePlaysBlack:
12104         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12105             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12106             return;
12107         }
12108         if (forwardMostMove < 2) return;
12109         currentMove = forwardMostMove = forwardMostMove - 2;
12110         whiteTimeRemaining = timeRemaining[0][currentMove];
12111         blackTimeRemaining = timeRemaining[1][currentMove];
12112         DisplayBothClocks();
12113         DisplayMove(currentMove - 1);
12114         ClearHighlights();/*!! could figure this out*/
12115         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12116         SendToProgram("remove\n", &first);
12117         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12118         break;
12119
12120       case BeginningOfGame:
12121       default:
12122         break;
12123
12124       case IcsPlayingWhite:
12125       case IcsPlayingBlack:
12126         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12127             SendToICS(ics_prefix);
12128             SendToICS("takeback 2\n");
12129         } else {
12130             SendToICS(ics_prefix);
12131             SendToICS("takeback 1\n");
12132         }
12133         break;
12134     }
12135 }
12136
12137 void
12138 MoveNowEvent()
12139 {
12140     ChessProgramState *cps;
12141
12142     switch (gameMode) {
12143       case MachinePlaysWhite:
12144         if (!WhiteOnMove(forwardMostMove)) {
12145             DisplayError(_("It is your turn"), 0);
12146             return;
12147         }
12148         cps = &first;
12149         break;
12150       case MachinePlaysBlack:
12151         if (WhiteOnMove(forwardMostMove)) {
12152             DisplayError(_("It is your turn"), 0);
12153             return;
12154         }
12155         cps = &first;
12156         break;
12157       case TwoMachinesPlay:
12158         if (WhiteOnMove(forwardMostMove) ==
12159             (first.twoMachinesColor[0] == 'w')) {
12160             cps = &first;
12161         } else {
12162             cps = &second;
12163         }
12164         break;
12165       case BeginningOfGame:
12166       default:
12167         return;
12168     }
12169     SendToProgram("?\n", cps);
12170 }
12171
12172 void
12173 TruncateGameEvent()
12174 {
12175     EditGameEvent();
12176     if (gameMode != EditGame) return;
12177     TruncateGame();
12178 }
12179
12180 void
12181 TruncateGame()
12182 {
12183     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12184     if (forwardMostMove > currentMove) {
12185         if (gameInfo.resultDetails != NULL) {
12186             free(gameInfo.resultDetails);
12187             gameInfo.resultDetails = NULL;
12188             gameInfo.result = GameUnfinished;
12189         }
12190         forwardMostMove = currentMove;
12191         HistorySet(parseList, backwardMostMove, forwardMostMove,
12192                    currentMove-1);
12193     }
12194 }
12195
12196 void
12197 HintEvent()
12198 {
12199     if (appData.noChessProgram) return;
12200     switch (gameMode) {
12201       case MachinePlaysWhite:
12202         if (WhiteOnMove(forwardMostMove)) {
12203             DisplayError(_("Wait until your turn"), 0);
12204             return;
12205         }
12206         break;
12207       case BeginningOfGame:
12208       case MachinePlaysBlack:
12209         if (!WhiteOnMove(forwardMostMove)) {
12210             DisplayError(_("Wait until your turn"), 0);
12211             return;
12212         }
12213         break;
12214       default:
12215         DisplayError(_("No hint available"), 0);
12216         return;
12217     }
12218     SendToProgram("hint\n", &first);
12219     hintRequested = TRUE;
12220 }
12221
12222 void
12223 BookEvent()
12224 {
12225     if (appData.noChessProgram) return;
12226     switch (gameMode) {
12227       case MachinePlaysWhite:
12228         if (WhiteOnMove(forwardMostMove)) {
12229             DisplayError(_("Wait until your turn"), 0);
12230             return;
12231         }
12232         break;
12233       case BeginningOfGame:
12234       case MachinePlaysBlack:
12235         if (!WhiteOnMove(forwardMostMove)) {
12236             DisplayError(_("Wait until your turn"), 0);
12237             return;
12238         }
12239         break;
12240       case EditPosition:
12241         EditPositionDone(TRUE);
12242         break;
12243       case TwoMachinesPlay:
12244         return;
12245       default:
12246         break;
12247     }
12248     SendToProgram("bk\n", &first);
12249     bookOutput[0] = NULLCHAR;
12250     bookRequested = TRUE;
12251 }
12252
12253 void
12254 AboutGameEvent()
12255 {
12256     char *tags = PGNTags(&gameInfo);
12257     TagsPopUp(tags, CmailMsg());
12258     free(tags);
12259 }
12260
12261 /* end button procedures */
12262
12263 void
12264 PrintPosition(fp, move)
12265      FILE *fp;
12266      int move;
12267 {
12268     int i, j;
12269     
12270     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12271         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12272             char c = PieceToChar(boards[move][i][j]);
12273             fputc(c == 'x' ? '.' : c, fp);
12274             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12275         }
12276     }
12277     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12278       fprintf(fp, "white to play\n");
12279     else
12280       fprintf(fp, "black to play\n");
12281 }
12282
12283 void
12284 PrintOpponents(fp)
12285      FILE *fp;
12286 {
12287     if (gameInfo.white != NULL) {
12288         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12289     } else {
12290         fprintf(fp, "\n");
12291     }
12292 }
12293
12294 /* Find last component of program's own name, using some heuristics */
12295 void
12296 TidyProgramName(prog, host, buf)
12297      char *prog, *host, buf[MSG_SIZ];
12298 {
12299     char *p, *q;
12300     int local = (strcmp(host, "localhost") == 0);
12301     while (!local && (p = strchr(prog, ';')) != NULL) {
12302         p++;
12303         while (*p == ' ') p++;
12304         prog = p;
12305     }
12306     if (*prog == '"' || *prog == '\'') {
12307         q = strchr(prog + 1, *prog);
12308     } else {
12309         q = strchr(prog, ' ');
12310     }
12311     if (q == NULL) q = prog + strlen(prog);
12312     p = q;
12313     while (p >= prog && *p != '/' && *p != '\\') p--;
12314     p++;
12315     if(p == prog && *p == '"') p++;
12316     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12317     memcpy(buf, p, q - p);
12318     buf[q - p] = NULLCHAR;
12319     if (!local) {
12320         strcat(buf, "@");
12321         strcat(buf, host);
12322     }
12323 }
12324
12325 char *
12326 TimeControlTagValue()
12327 {
12328     char buf[MSG_SIZ];
12329     if (!appData.clockMode) {
12330         strcpy(buf, "-");
12331     } else if (movesPerSession > 0) {
12332         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12333     } else if (timeIncrement == 0) {
12334         sprintf(buf, "%ld", timeControl/1000);
12335     } else {
12336         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12337     }
12338     return StrSave(buf);
12339 }
12340
12341 void
12342 SetGameInfo()
12343 {
12344     /* This routine is used only for certain modes */
12345     VariantClass v = gameInfo.variant;
12346     ChessMove r = GameUnfinished;
12347     char *p = NULL;
12348
12349     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12350         r = gameInfo.result; 
12351         p = gameInfo.resultDetails; 
12352         gameInfo.resultDetails = NULL;
12353     }
12354     ClearGameInfo(&gameInfo);
12355     gameInfo.variant = v;
12356
12357     switch (gameMode) {
12358       case MachinePlaysWhite:
12359         gameInfo.event = StrSave( appData.pgnEventHeader );
12360         gameInfo.site = StrSave(HostName());
12361         gameInfo.date = PGNDate();
12362         gameInfo.round = StrSave("-");
12363         gameInfo.white = StrSave(first.tidy);
12364         gameInfo.black = StrSave(UserName());
12365         gameInfo.timeControl = TimeControlTagValue();
12366         break;
12367
12368       case MachinePlaysBlack:
12369         gameInfo.event = StrSave( appData.pgnEventHeader );
12370         gameInfo.site = StrSave(HostName());
12371         gameInfo.date = PGNDate();
12372         gameInfo.round = StrSave("-");
12373         gameInfo.white = StrSave(UserName());
12374         gameInfo.black = StrSave(first.tidy);
12375         gameInfo.timeControl = TimeControlTagValue();
12376         break;
12377
12378       case TwoMachinesPlay:
12379         gameInfo.event = StrSave( appData.pgnEventHeader );
12380         gameInfo.site = StrSave(HostName());
12381         gameInfo.date = PGNDate();
12382         if (matchGame > 0) {
12383             char buf[MSG_SIZ];
12384             sprintf(buf, "%d", matchGame);
12385             gameInfo.round = StrSave(buf);
12386         } else {
12387             gameInfo.round = StrSave("-");
12388         }
12389         if (first.twoMachinesColor[0] == 'w') {
12390             gameInfo.white = StrSave(first.tidy);
12391             gameInfo.black = StrSave(second.tidy);
12392         } else {
12393             gameInfo.white = StrSave(second.tidy);
12394             gameInfo.black = StrSave(first.tidy);
12395         }
12396         gameInfo.timeControl = TimeControlTagValue();
12397         break;
12398
12399       case EditGame:
12400         gameInfo.event = StrSave("Edited game");
12401         gameInfo.site = StrSave(HostName());
12402         gameInfo.date = PGNDate();
12403         gameInfo.round = StrSave("-");
12404         gameInfo.white = StrSave("-");
12405         gameInfo.black = StrSave("-");
12406         gameInfo.result = r;
12407         gameInfo.resultDetails = p;
12408         break;
12409
12410       case EditPosition:
12411         gameInfo.event = StrSave("Edited position");
12412         gameInfo.site = StrSave(HostName());
12413         gameInfo.date = PGNDate();
12414         gameInfo.round = StrSave("-");
12415         gameInfo.white = StrSave("-");
12416         gameInfo.black = StrSave("-");
12417         break;
12418
12419       case IcsPlayingWhite:
12420       case IcsPlayingBlack:
12421       case IcsObserving:
12422       case IcsExamining:
12423         break;
12424
12425       case PlayFromGameFile:
12426         gameInfo.event = StrSave("Game from non-PGN file");
12427         gameInfo.site = StrSave(HostName());
12428         gameInfo.date = PGNDate();
12429         gameInfo.round = StrSave("-");
12430         gameInfo.white = StrSave("?");
12431         gameInfo.black = StrSave("?");
12432         break;
12433
12434       default:
12435         break;
12436     }
12437 }
12438
12439 void
12440 ReplaceComment(index, text)
12441      int index;
12442      char *text;
12443 {
12444     int len;
12445
12446     while (*text == '\n') text++;
12447     len = strlen(text);
12448     while (len > 0 && text[len - 1] == '\n') len--;
12449
12450     if (commentList[index] != NULL)
12451       free(commentList[index]);
12452
12453     if (len == 0) {
12454         commentList[index] = NULL;
12455         return;
12456     }
12457   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12458       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12459       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12460     commentList[index] = (char *) malloc(len + 2);
12461     strncpy(commentList[index], text, len);
12462     commentList[index][len] = '\n';
12463     commentList[index][len + 1] = NULLCHAR;
12464   } else { 
12465     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12466     char *p;
12467     commentList[index] = (char *) malloc(len + 6);
12468     strcpy(commentList[index], "{\n");
12469     strncpy(commentList[index]+2, text, len);
12470     commentList[index][len+2] = NULLCHAR;
12471     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12472     strcat(commentList[index], "\n}\n");
12473   }
12474 }
12475
12476 void
12477 CrushCRs(text)
12478      char *text;
12479 {
12480   char *p = text;
12481   char *q = text;
12482   char ch;
12483
12484   do {
12485     ch = *p++;
12486     if (ch == '\r') continue;
12487     *q++ = ch;
12488   } while (ch != '\0');
12489 }
12490
12491 void
12492 AppendComment(index, text, addBraces)
12493      int index;
12494      char *text;
12495      Boolean addBraces; // [HGM] braces: tells if we should add {}
12496 {
12497     int oldlen, len;
12498     char *old;
12499
12500 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12501     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12502
12503     CrushCRs(text);
12504     while (*text == '\n') text++;
12505     len = strlen(text);
12506     while (len > 0 && text[len - 1] == '\n') len--;
12507
12508     if (len == 0) return;
12509
12510     if (commentList[index] != NULL) {
12511         old = commentList[index];
12512         oldlen = strlen(old);
12513         while(commentList[index][oldlen-1] ==  '\n')
12514           commentList[index][--oldlen] = NULLCHAR;
12515         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12516         strcpy(commentList[index], old);
12517         free(old);
12518         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12519         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12520           if(addBraces) addBraces = FALSE; else { text++; len--; }
12521           while (*text == '\n') { text++; len--; }
12522           commentList[index][--oldlen] = NULLCHAR;
12523       }
12524         if(addBraces) strcat(commentList[index], "\n{\n");
12525         else          strcat(commentList[index], "\n");
12526         strcat(commentList[index], text);
12527         if(addBraces) strcat(commentList[index], "\n}\n");
12528         else          strcat(commentList[index], "\n");
12529     } else {
12530         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12531         if(addBraces)
12532              strcpy(commentList[index], "{\n");
12533         else commentList[index][0] = NULLCHAR;
12534         strcat(commentList[index], text);
12535         strcat(commentList[index], "\n");
12536         if(addBraces) strcat(commentList[index], "}\n");
12537     }
12538 }
12539
12540 static char * FindStr( char * text, char * sub_text )
12541 {
12542     char * result = strstr( text, sub_text );
12543
12544     if( result != NULL ) {
12545         result += strlen( sub_text );
12546     }
12547
12548     return result;
12549 }
12550
12551 /* [AS] Try to extract PV info from PGN comment */
12552 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12553 char *GetInfoFromComment( int index, char * text )
12554 {
12555     char * sep = text;
12556
12557     if( text != NULL && index > 0 ) {
12558         int score = 0;
12559         int depth = 0;
12560         int time = -1, sec = 0, deci;
12561         char * s_eval = FindStr( text, "[%eval " );
12562         char * s_emt = FindStr( text, "[%emt " );
12563
12564         if( s_eval != NULL || s_emt != NULL ) {
12565             /* New style */
12566             char delim;
12567
12568             if( s_eval != NULL ) {
12569                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12570                     return text;
12571                 }
12572
12573                 if( delim != ']' ) {
12574                     return text;
12575                 }
12576             }
12577
12578             if( s_emt != NULL ) {
12579             }
12580                 return text;
12581         }
12582         else {
12583             /* We expect something like: [+|-]nnn.nn/dd */
12584             int score_lo = 0;
12585
12586             if(*text != '{') return text; // [HGM] braces: must be normal comment
12587
12588             sep = strchr( text, '/' );
12589             if( sep == NULL || sep < (text+4) ) {
12590                 return text;
12591             }
12592
12593             time = -1; sec = -1; deci = -1;
12594             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12595                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12596                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12597                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12598                 return text;
12599             }
12600
12601             if( score_lo < 0 || score_lo >= 100 ) {
12602                 return text;
12603             }
12604
12605             if(sec >= 0) time = 600*time + 10*sec; else
12606             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12607
12608             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12609
12610             /* [HGM] PV time: now locate end of PV info */
12611             while( *++sep >= '0' && *sep <= '9'); // strip depth
12612             if(time >= 0)
12613             while( *++sep >= '0' && *sep <= '9'); // strip time
12614             if(sec >= 0)
12615             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12616             if(deci >= 0)
12617             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12618             while(*sep == ' ') sep++;
12619         }
12620
12621         if( depth <= 0 ) {
12622             return text;
12623         }
12624
12625         if( time < 0 ) {
12626             time = -1;
12627         }
12628
12629         pvInfoList[index-1].depth = depth;
12630         pvInfoList[index-1].score = score;
12631         pvInfoList[index-1].time  = 10*time; // centi-sec
12632         if(*sep == '}') *sep = 0; else *--sep = '{';
12633     }
12634     return sep;
12635 }
12636
12637 void
12638 SendToProgram(message, cps)
12639      char *message;
12640      ChessProgramState *cps;
12641 {
12642     int count, outCount, error;
12643     char buf[MSG_SIZ];
12644
12645     if (cps->pr == NULL) return;
12646     Attention(cps);
12647     
12648     if (appData.debugMode) {
12649         TimeMark now;
12650         GetTimeMark(&now);
12651         fprintf(debugFP, "%ld >%-6s: %s", 
12652                 SubtractTimeMarks(&now, &programStartTime),
12653                 cps->which, message);
12654     }
12655     
12656     count = strlen(message);
12657     outCount = OutputToProcess(cps->pr, message, count, &error);
12658     if (outCount < count && !exiting 
12659                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12660         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12661         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12662             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12663                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12664                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12665             } else {
12666                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12667             }
12668             gameInfo.resultDetails = StrSave(buf);
12669         }
12670         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12671     }
12672 }
12673
12674 void
12675 ReceiveFromProgram(isr, closure, message, count, error)
12676      InputSourceRef isr;
12677      VOIDSTAR closure;
12678      char *message;
12679      int count;
12680      int error;
12681 {
12682     char *end_str;
12683     char buf[MSG_SIZ];
12684     ChessProgramState *cps = (ChessProgramState *)closure;
12685
12686     if (isr != cps->isr) return; /* Killed intentionally */
12687     if (count <= 0) {
12688         if (count == 0) {
12689             sprintf(buf,
12690                     _("Error: %s chess program (%s) exited unexpectedly"),
12691                     cps->which, cps->program);
12692         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12693                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12694                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12695                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12696                 } else {
12697                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12698                 }
12699                 gameInfo.resultDetails = StrSave(buf);
12700             }
12701             RemoveInputSource(cps->isr);
12702             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12703         } else {
12704             sprintf(buf,
12705                     _("Error reading from %s chess program (%s)"),
12706                     cps->which, cps->program);
12707             RemoveInputSource(cps->isr);
12708
12709             /* [AS] Program is misbehaving badly... kill it */
12710             if( count == -2 ) {
12711                 DestroyChildProcess( cps->pr, 9 );
12712                 cps->pr = NoProc;
12713             }
12714
12715             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12716         }
12717         return;
12718     }
12719     
12720     if ((end_str = strchr(message, '\r')) != NULL)
12721       *end_str = NULLCHAR;
12722     if ((end_str = strchr(message, '\n')) != NULL)
12723       *end_str = NULLCHAR;
12724     
12725     if (appData.debugMode) {
12726         TimeMark now; int print = 1;
12727         char *quote = ""; char c; int i;
12728
12729         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12730                 char start = message[0];
12731                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12732                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12733                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12734                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12735                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12736                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12737                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12738                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12739                         { quote = "# "; print = (appData.engineComments == 2); }
12740                 message[0] = start; // restore original message
12741         }
12742         if(print) {
12743                 GetTimeMark(&now);
12744                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12745                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12746                         quote,
12747                         message);
12748         }
12749     }
12750
12751     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12752     if (appData.icsEngineAnalyze) {
12753         if (strstr(message, "whisper") != NULL ||
12754              strstr(message, "kibitz") != NULL || 
12755             strstr(message, "tellics") != NULL) return;
12756     }
12757
12758     HandleMachineMove(message, cps);
12759 }
12760
12761
12762 void
12763 SendTimeControl(cps, mps, tc, inc, sd, st)
12764      ChessProgramState *cps;
12765      int mps, inc, sd, st;
12766      long tc;
12767 {
12768     char buf[MSG_SIZ];
12769     int seconds;
12770
12771     if( timeControl_2 > 0 ) {
12772         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12773             tc = timeControl_2;
12774         }
12775     }
12776     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12777     inc /= cps->timeOdds;
12778     st  /= cps->timeOdds;
12779
12780     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12781
12782     if (st > 0) {
12783       /* Set exact time per move, normally using st command */
12784       if (cps->stKludge) {
12785         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12786         seconds = st % 60;
12787         if (seconds == 0) {
12788           sprintf(buf, "level 1 %d\n", st/60);
12789         } else {
12790           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12791         }
12792       } else {
12793         sprintf(buf, "st %d\n", st);
12794       }
12795     } else {
12796       /* Set conventional or incremental time control, using level command */
12797       if (seconds == 0) {
12798         /* Note old gnuchess bug -- minutes:seconds used to not work.
12799            Fixed in later versions, but still avoid :seconds
12800            when seconds is 0. */
12801         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12802       } else {
12803         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12804                 seconds, inc/1000);
12805       }
12806     }
12807     SendToProgram(buf, cps);
12808
12809     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12810     /* Orthogonally, limit search to given depth */
12811     if (sd > 0) {
12812       if (cps->sdKludge) {
12813         sprintf(buf, "depth\n%d\n", sd);
12814       } else {
12815         sprintf(buf, "sd %d\n", sd);
12816       }
12817       SendToProgram(buf, cps);
12818     }
12819
12820     if(cps->nps > 0) { /* [HGM] nps */
12821         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12822         else {
12823                 sprintf(buf, "nps %d\n", cps->nps);
12824               SendToProgram(buf, cps);
12825         }
12826     }
12827 }
12828
12829 ChessProgramState *WhitePlayer()
12830 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12831 {
12832     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12833        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12834         return &second;
12835     return &first;
12836 }
12837
12838 void
12839 SendTimeRemaining(cps, machineWhite)
12840      ChessProgramState *cps;
12841      int /*boolean*/ machineWhite;
12842 {
12843     char message[MSG_SIZ];
12844     long time, otime;
12845
12846     /* Note: this routine must be called when the clocks are stopped
12847        or when they have *just* been set or switched; otherwise
12848        it will be off by the time since the current tick started.
12849     */
12850     if (machineWhite) {
12851         time = whiteTimeRemaining / 10;
12852         otime = blackTimeRemaining / 10;
12853     } else {
12854         time = blackTimeRemaining / 10;
12855         otime = whiteTimeRemaining / 10;
12856     }
12857     /* [HGM] translate opponent's time by time-odds factor */
12858     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12859     if (appData.debugMode) {
12860         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12861     }
12862
12863     if (time <= 0) time = 1;
12864     if (otime <= 0) otime = 1;
12865     
12866     sprintf(message, "time %ld\n", time);
12867     SendToProgram(message, cps);
12868
12869     sprintf(message, "otim %ld\n", otime);
12870     SendToProgram(message, cps);
12871 }
12872
12873 int
12874 BoolFeature(p, name, loc, cps)
12875      char **p;
12876      char *name;
12877      int *loc;
12878      ChessProgramState *cps;
12879 {
12880   char buf[MSG_SIZ];
12881   int len = strlen(name);
12882   int val;
12883   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12884     (*p) += len + 1;
12885     sscanf(*p, "%d", &val);
12886     *loc = (val != 0);
12887     while (**p && **p != ' ') (*p)++;
12888     sprintf(buf, "accepted %s\n", name);
12889     SendToProgram(buf, cps);
12890     return TRUE;
12891   }
12892   return FALSE;
12893 }
12894
12895 int
12896 IntFeature(p, name, loc, cps)
12897      char **p;
12898      char *name;
12899      int *loc;
12900      ChessProgramState *cps;
12901 {
12902   char buf[MSG_SIZ];
12903   int len = strlen(name);
12904   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12905     (*p) += len + 1;
12906     sscanf(*p, "%d", loc);
12907     while (**p && **p != ' ') (*p)++;
12908     sprintf(buf, "accepted %s\n", name);
12909     SendToProgram(buf, cps);
12910     return TRUE;
12911   }
12912   return FALSE;
12913 }
12914
12915 int
12916 StringFeature(p, name, loc, cps)
12917      char **p;
12918      char *name;
12919      char loc[];
12920      ChessProgramState *cps;
12921 {
12922   char buf[MSG_SIZ];
12923   int len = strlen(name);
12924   if (strncmp((*p), name, len) == 0
12925       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12926     (*p) += len + 2;
12927     sscanf(*p, "%[^\"]", loc);
12928     while (**p && **p != '\"') (*p)++;
12929     if (**p == '\"') (*p)++;
12930     sprintf(buf, "accepted %s\n", name);
12931     SendToProgram(buf, cps);
12932     return TRUE;
12933   }
12934   return FALSE;
12935 }
12936
12937 int 
12938 ParseOption(Option *opt, ChessProgramState *cps)
12939 // [HGM] options: process the string that defines an engine option, and determine
12940 // name, type, default value, and allowed value range
12941 {
12942         char *p, *q, buf[MSG_SIZ];
12943         int n, min = (-1)<<31, max = 1<<31, def;
12944
12945         if(p = strstr(opt->name, " -spin ")) {
12946             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12947             if(max < min) max = min; // enforce consistency
12948             if(def < min) def = min;
12949             if(def > max) def = max;
12950             opt->value = def;
12951             opt->min = min;
12952             opt->max = max;
12953             opt->type = Spin;
12954         } else if((p = strstr(opt->name, " -slider "))) {
12955             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12956             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12957             if(max < min) max = min; // enforce consistency
12958             if(def < min) def = min;
12959             if(def > max) def = max;
12960             opt->value = def;
12961             opt->min = min;
12962             opt->max = max;
12963             opt->type = Spin; // Slider;
12964         } else if((p = strstr(opt->name, " -string "))) {
12965             opt->textValue = p+9;
12966             opt->type = TextBox;
12967         } else if((p = strstr(opt->name, " -file "))) {
12968             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12969             opt->textValue = p+7;
12970             opt->type = TextBox; // FileName;
12971         } else if((p = strstr(opt->name, " -path "))) {
12972             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12973             opt->textValue = p+7;
12974             opt->type = TextBox; // PathName;
12975         } else if(p = strstr(opt->name, " -check ")) {
12976             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12977             opt->value = (def != 0);
12978             opt->type = CheckBox;
12979         } else if(p = strstr(opt->name, " -combo ")) {
12980             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12981             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12982             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12983             opt->value = n = 0;
12984             while(q = StrStr(q, " /// ")) {
12985                 n++; *q = 0;    // count choices, and null-terminate each of them
12986                 q += 5;
12987                 if(*q == '*') { // remember default, which is marked with * prefix
12988                     q++;
12989                     opt->value = n;
12990                 }
12991                 cps->comboList[cps->comboCnt++] = q;
12992             }
12993             cps->comboList[cps->comboCnt++] = NULL;
12994             opt->max = n + 1;
12995             opt->type = ComboBox;
12996         } else if(p = strstr(opt->name, " -button")) {
12997             opt->type = Button;
12998         } else if(p = strstr(opt->name, " -save")) {
12999             opt->type = SaveButton;
13000         } else return FALSE;
13001         *p = 0; // terminate option name
13002         // now look if the command-line options define a setting for this engine option.
13003         if(cps->optionSettings && cps->optionSettings[0])
13004             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13005         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13006                 sprintf(buf, "option %s", p);
13007                 if(p = strstr(buf, ",")) *p = 0;
13008                 strcat(buf, "\n");
13009                 SendToProgram(buf, cps);
13010         }
13011         return TRUE;
13012 }
13013
13014 void
13015 FeatureDone(cps, val)
13016      ChessProgramState* cps;
13017      int val;
13018 {
13019   DelayedEventCallback cb = GetDelayedEvent();
13020   if ((cb == InitBackEnd3 && cps == &first) ||
13021       (cb == TwoMachinesEventIfReady && cps == &second)) {
13022     CancelDelayedEvent();
13023     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13024   }
13025   cps->initDone = val;
13026 }
13027
13028 /* Parse feature command from engine */
13029 void
13030 ParseFeatures(args, cps)
13031      char* args;
13032      ChessProgramState *cps;  
13033 {
13034   char *p = args;
13035   char *q;
13036   int val;
13037   char buf[MSG_SIZ];
13038
13039   for (;;) {
13040     while (*p == ' ') p++;
13041     if (*p == NULLCHAR) return;
13042
13043     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13044     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13045     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13046     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13047     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13048     if (BoolFeature(&p, "reuse", &val, cps)) {
13049       /* Engine can disable reuse, but can't enable it if user said no */
13050       if (!val) cps->reuse = FALSE;
13051       continue;
13052     }
13053     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13054     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13055       if (gameMode == TwoMachinesPlay) {
13056         DisplayTwoMachinesTitle();
13057       } else {
13058         DisplayTitle("");
13059       }
13060       continue;
13061     }
13062     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13063     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13064     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13065     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13066     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13067     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13068     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13069     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13070     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13071     if (IntFeature(&p, "done", &val, cps)) {
13072       FeatureDone(cps, val);
13073       continue;
13074     }
13075     /* Added by Tord: */
13076     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13077     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13078     /* End of additions by Tord */
13079
13080     /* [HGM] added features: */
13081     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13082     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13083     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13084     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13085     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13086     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13087     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13088         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13089             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13090             SendToProgram(buf, cps);
13091             continue;
13092         }
13093         if(cps->nrOptions >= MAX_OPTIONS) {
13094             cps->nrOptions--;
13095             sprintf(buf, "%s engine has too many options\n", cps->which);
13096             DisplayError(buf, 0);
13097         }
13098         continue;
13099     }
13100     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13101     /* End of additions by HGM */
13102
13103     /* unknown feature: complain and skip */
13104     q = p;
13105     while (*q && *q != '=') q++;
13106     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13107     SendToProgram(buf, cps);
13108     p = q;
13109     if (*p == '=') {
13110       p++;
13111       if (*p == '\"') {
13112         p++;
13113         while (*p && *p != '\"') p++;
13114         if (*p == '\"') p++;
13115       } else {
13116         while (*p && *p != ' ') p++;
13117       }
13118     }
13119   }
13120
13121 }
13122
13123 void
13124 PeriodicUpdatesEvent(newState)
13125      int newState;
13126 {
13127     if (newState == appData.periodicUpdates)
13128       return;
13129
13130     appData.periodicUpdates=newState;
13131
13132     /* Display type changes, so update it now */
13133 //    DisplayAnalysis();
13134
13135     /* Get the ball rolling again... */
13136     if (newState) {
13137         AnalysisPeriodicEvent(1);
13138         StartAnalysisClock();
13139     }
13140 }
13141
13142 void
13143 PonderNextMoveEvent(newState)
13144      int newState;
13145 {
13146     if (newState == appData.ponderNextMove) return;
13147     if (gameMode == EditPosition) EditPositionDone(TRUE);
13148     if (newState) {
13149         SendToProgram("hard\n", &first);
13150         if (gameMode == TwoMachinesPlay) {
13151             SendToProgram("hard\n", &second);
13152         }
13153     } else {
13154         SendToProgram("easy\n", &first);
13155         thinkOutput[0] = NULLCHAR;
13156         if (gameMode == TwoMachinesPlay) {
13157             SendToProgram("easy\n", &second);
13158         }
13159     }
13160     appData.ponderNextMove = newState;
13161 }
13162
13163 void
13164 NewSettingEvent(option, command, value)
13165      char *command;
13166      int option, value;
13167 {
13168     char buf[MSG_SIZ];
13169
13170     if (gameMode == EditPosition) EditPositionDone(TRUE);
13171     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13172     SendToProgram(buf, &first);
13173     if (gameMode == TwoMachinesPlay) {
13174         SendToProgram(buf, &second);
13175     }
13176 }
13177
13178 void
13179 ShowThinkingEvent()
13180 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13181 {
13182     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13183     int newState = appData.showThinking
13184         // [HGM] thinking: other features now need thinking output as well
13185         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13186     
13187     if (oldState == newState) return;
13188     oldState = newState;
13189     if (gameMode == EditPosition) EditPositionDone(TRUE);
13190     if (oldState) {
13191         SendToProgram("post\n", &first);
13192         if (gameMode == TwoMachinesPlay) {
13193             SendToProgram("post\n", &second);
13194         }
13195     } else {
13196         SendToProgram("nopost\n", &first);
13197         thinkOutput[0] = NULLCHAR;
13198         if (gameMode == TwoMachinesPlay) {
13199             SendToProgram("nopost\n", &second);
13200         }
13201     }
13202 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13203 }
13204
13205 void
13206 AskQuestionEvent(title, question, replyPrefix, which)
13207      char *title; char *question; char *replyPrefix; char *which;
13208 {
13209   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13210   if (pr == NoProc) return;
13211   AskQuestion(title, question, replyPrefix, pr);
13212 }
13213
13214 void
13215 DisplayMove(moveNumber)
13216      int moveNumber;
13217 {
13218     char message[MSG_SIZ];
13219     char res[MSG_SIZ];
13220     char cpThinkOutput[MSG_SIZ];
13221
13222     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13223     
13224     if (moveNumber == forwardMostMove - 1 || 
13225         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13226
13227         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13228
13229         if (strchr(cpThinkOutput, '\n')) {
13230             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13231         }
13232     } else {
13233         *cpThinkOutput = NULLCHAR;
13234     }
13235
13236     /* [AS] Hide thinking from human user */
13237     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13238         *cpThinkOutput = NULLCHAR;
13239         if( thinkOutput[0] != NULLCHAR ) {
13240             int i;
13241
13242             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13243                 cpThinkOutput[i] = '.';
13244             }
13245             cpThinkOutput[i] = NULLCHAR;
13246             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13247         }
13248     }
13249
13250     if (moveNumber == forwardMostMove - 1 &&
13251         gameInfo.resultDetails != NULL) {
13252         if (gameInfo.resultDetails[0] == NULLCHAR) {
13253             sprintf(res, " %s", PGNResult(gameInfo.result));
13254         } else {
13255             sprintf(res, " {%s} %s",
13256                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13257         }
13258     } else {
13259         res[0] = NULLCHAR;
13260     }
13261
13262     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13263         DisplayMessage(res, cpThinkOutput);
13264     } else {
13265         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13266                 WhiteOnMove(moveNumber) ? " " : ".. ",
13267                 parseList[moveNumber], res);
13268         DisplayMessage(message, cpThinkOutput);
13269     }
13270 }
13271
13272 void
13273 DisplayComment(moveNumber, text)
13274      int moveNumber;
13275      char *text;
13276 {
13277     char title[MSG_SIZ];
13278     char buf[8000]; // comment can be long!
13279     int score, depth;
13280     
13281     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13282       strcpy(title, "Comment");
13283     } else {
13284       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13285               WhiteOnMove(moveNumber) ? " " : ".. ",
13286               parseList[moveNumber]);
13287     }
13288     // [HGM] PV info: display PV info together with (or as) comment
13289     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13290       if(text == NULL) text = "";                                           
13291       score = pvInfoList[moveNumber].score;
13292       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13293               depth, (pvInfoList[moveNumber].time+50)/100, text);
13294       text = buf;
13295     }
13296     if (text != NULL && (appData.autoDisplayComment || commentUp))
13297         CommentPopUp(title, text);
13298 }
13299
13300 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13301  * might be busy thinking or pondering.  It can be omitted if your
13302  * gnuchess is configured to stop thinking immediately on any user
13303  * input.  However, that gnuchess feature depends on the FIONREAD
13304  * ioctl, which does not work properly on some flavors of Unix.
13305  */
13306 void
13307 Attention(cps)
13308      ChessProgramState *cps;
13309 {
13310 #if ATTENTION
13311     if (!cps->useSigint) return;
13312     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13313     switch (gameMode) {
13314       case MachinePlaysWhite:
13315       case MachinePlaysBlack:
13316       case TwoMachinesPlay:
13317       case IcsPlayingWhite:
13318       case IcsPlayingBlack:
13319       case AnalyzeMode:
13320       case AnalyzeFile:
13321         /* Skip if we know it isn't thinking */
13322         if (!cps->maybeThinking) return;
13323         if (appData.debugMode)
13324           fprintf(debugFP, "Interrupting %s\n", cps->which);
13325         InterruptChildProcess(cps->pr);
13326         cps->maybeThinking = FALSE;
13327         break;
13328       default:
13329         break;
13330     }
13331 #endif /*ATTENTION*/
13332 }
13333
13334 int
13335 CheckFlags()
13336 {
13337     if (whiteTimeRemaining <= 0) {
13338         if (!whiteFlag) {
13339             whiteFlag = TRUE;
13340             if (appData.icsActive) {
13341                 if (appData.autoCallFlag &&
13342                     gameMode == IcsPlayingBlack && !blackFlag) {
13343                   SendToICS(ics_prefix);
13344                   SendToICS("flag\n");
13345                 }
13346             } else {
13347                 if (blackFlag) {
13348                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13349                 } else {
13350                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13351                     if (appData.autoCallFlag) {
13352                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13353                         return TRUE;
13354                     }
13355                 }
13356             }
13357         }
13358     }
13359     if (blackTimeRemaining <= 0) {
13360         if (!blackFlag) {
13361             blackFlag = TRUE;
13362             if (appData.icsActive) {
13363                 if (appData.autoCallFlag &&
13364                     gameMode == IcsPlayingWhite && !whiteFlag) {
13365                   SendToICS(ics_prefix);
13366                   SendToICS("flag\n");
13367                 }
13368             } else {
13369                 if (whiteFlag) {
13370                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13371                 } else {
13372                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13373                     if (appData.autoCallFlag) {
13374                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13375                         return TRUE;
13376                     }
13377                 }
13378             }
13379         }
13380     }
13381     return FALSE;
13382 }
13383
13384 void
13385 CheckTimeControl()
13386 {
13387     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13388         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13389
13390     /*
13391      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13392      */
13393     if ( !WhiteOnMove(forwardMostMove) )
13394         /* White made time control */
13395         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13396         /* [HGM] time odds: correct new time quota for time odds! */
13397                                             / WhitePlayer()->timeOdds;
13398       else
13399         /* Black made time control */
13400         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13401                                             / WhitePlayer()->other->timeOdds;
13402 }
13403
13404 void
13405 DisplayBothClocks()
13406 {
13407     int wom = gameMode == EditPosition ?
13408       !blackPlaysFirst : WhiteOnMove(currentMove);
13409     DisplayWhiteClock(whiteTimeRemaining, wom);
13410     DisplayBlackClock(blackTimeRemaining, !wom);
13411 }
13412
13413
13414 /* Timekeeping seems to be a portability nightmare.  I think everyone
13415    has ftime(), but I'm really not sure, so I'm including some ifdefs
13416    to use other calls if you don't.  Clocks will be less accurate if
13417    you have neither ftime nor gettimeofday.
13418 */
13419
13420 /* VS 2008 requires the #include outside of the function */
13421 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13422 #include <sys/timeb.h>
13423 #endif
13424
13425 /* Get the current time as a TimeMark */
13426 void
13427 GetTimeMark(tm)
13428      TimeMark *tm;
13429 {
13430 #if HAVE_GETTIMEOFDAY
13431
13432     struct timeval timeVal;
13433     struct timezone timeZone;
13434
13435     gettimeofday(&timeVal, &timeZone);
13436     tm->sec = (long) timeVal.tv_sec; 
13437     tm->ms = (int) (timeVal.tv_usec / 1000L);
13438
13439 #else /*!HAVE_GETTIMEOFDAY*/
13440 #if HAVE_FTIME
13441
13442 // include <sys/timeb.h> / moved to just above start of function
13443     struct timeb timeB;
13444
13445     ftime(&timeB);
13446     tm->sec = (long) timeB.time;
13447     tm->ms = (int) timeB.millitm;
13448
13449 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13450     tm->sec = (long) time(NULL);
13451     tm->ms = 0;
13452 #endif
13453 #endif
13454 }
13455
13456 /* Return the difference in milliseconds between two
13457    time marks.  We assume the difference will fit in a long!
13458 */
13459 long
13460 SubtractTimeMarks(tm2, tm1)
13461      TimeMark *tm2, *tm1;
13462 {
13463     return 1000L*(tm2->sec - tm1->sec) +
13464            (long) (tm2->ms - tm1->ms);
13465 }
13466
13467
13468 /*
13469  * Code to manage the game clocks.
13470  *
13471  * In tournament play, black starts the clock and then white makes a move.
13472  * We give the human user a slight advantage if he is playing white---the
13473  * clocks don't run until he makes his first move, so it takes zero time.
13474  * Also, we don't account for network lag, so we could get out of sync
13475  * with GNU Chess's clock -- but then, referees are always right.  
13476  */
13477
13478 static TimeMark tickStartTM;
13479 static long intendedTickLength;
13480
13481 long
13482 NextTickLength(timeRemaining)
13483      long timeRemaining;
13484 {
13485     long nominalTickLength, nextTickLength;
13486
13487     if (timeRemaining > 0L && timeRemaining <= 10000L)
13488       nominalTickLength = 100L;
13489     else
13490       nominalTickLength = 1000L;
13491     nextTickLength = timeRemaining % nominalTickLength;
13492     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13493
13494     return nextTickLength;
13495 }
13496
13497 /* Adjust clock one minute up or down */
13498 void
13499 AdjustClock(Boolean which, int dir)
13500 {
13501     if(which) blackTimeRemaining += 60000*dir;
13502     else      whiteTimeRemaining += 60000*dir;
13503     DisplayBothClocks();
13504 }
13505
13506 /* Stop clocks and reset to a fresh time control */
13507 void
13508 ResetClocks() 
13509 {
13510     (void) StopClockTimer();
13511     if (appData.icsActive) {
13512         whiteTimeRemaining = blackTimeRemaining = 0;
13513     } else if (searchTime) {
13514         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13515         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13516     } else { /* [HGM] correct new time quote for time odds */
13517         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13518         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13519     }
13520     if (whiteFlag || blackFlag) {
13521         DisplayTitle("");
13522         whiteFlag = blackFlag = FALSE;
13523     }
13524     DisplayBothClocks();
13525 }
13526
13527 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13528
13529 /* Decrement running clock by amount of time that has passed */
13530 void
13531 DecrementClocks()
13532 {
13533     long timeRemaining;
13534     long lastTickLength, fudge;
13535     TimeMark now;
13536
13537     if (!appData.clockMode) return;
13538     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13539         
13540     GetTimeMark(&now);
13541
13542     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13543
13544     /* Fudge if we woke up a little too soon */
13545     fudge = intendedTickLength - lastTickLength;
13546     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13547
13548     if (WhiteOnMove(forwardMostMove)) {
13549         if(whiteNPS >= 0) lastTickLength = 0;
13550         timeRemaining = whiteTimeRemaining -= lastTickLength;
13551         DisplayWhiteClock(whiteTimeRemaining - fudge,
13552                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13553     } else {
13554         if(blackNPS >= 0) lastTickLength = 0;
13555         timeRemaining = blackTimeRemaining -= lastTickLength;
13556         DisplayBlackClock(blackTimeRemaining - fudge,
13557                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13558     }
13559
13560     if (CheckFlags()) return;
13561         
13562     tickStartTM = now;
13563     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13564     StartClockTimer(intendedTickLength);
13565
13566     /* if the time remaining has fallen below the alarm threshold, sound the
13567      * alarm. if the alarm has sounded and (due to a takeback or time control
13568      * with increment) the time remaining has increased to a level above the
13569      * threshold, reset the alarm so it can sound again. 
13570      */
13571     
13572     if (appData.icsActive && appData.icsAlarm) {
13573
13574         /* make sure we are dealing with the user's clock */
13575         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13576                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13577            )) return;
13578
13579         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13580             alarmSounded = FALSE;
13581         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13582             PlayAlarmSound();
13583             alarmSounded = TRUE;
13584         }
13585     }
13586 }
13587
13588
13589 /* A player has just moved, so stop the previously running
13590    clock and (if in clock mode) start the other one.
13591    We redisplay both clocks in case we're in ICS mode, because
13592    ICS gives us an update to both clocks after every move.
13593    Note that this routine is called *after* forwardMostMove
13594    is updated, so the last fractional tick must be subtracted
13595    from the color that is *not* on move now.
13596 */
13597 void
13598 SwitchClocks()
13599 {
13600     long lastTickLength;
13601     TimeMark now;
13602     int flagged = FALSE;
13603
13604     GetTimeMark(&now);
13605
13606     if (StopClockTimer() && appData.clockMode) {
13607         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13608         if (WhiteOnMove(forwardMostMove)) {
13609             if(blackNPS >= 0) lastTickLength = 0;
13610             blackTimeRemaining -= lastTickLength;
13611            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13612 //         if(pvInfoList[forwardMostMove-1].time == -1)
13613                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13614                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13615         } else {
13616            if(whiteNPS >= 0) lastTickLength = 0;
13617            whiteTimeRemaining -= lastTickLength;
13618            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13619 //         if(pvInfoList[forwardMostMove-1].time == -1)
13620                  pvInfoList[forwardMostMove-1].time = 
13621                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13622         }
13623         flagged = CheckFlags();
13624     }
13625     CheckTimeControl();
13626
13627     if (flagged || !appData.clockMode) return;
13628
13629     switch (gameMode) {
13630       case MachinePlaysBlack:
13631       case MachinePlaysWhite:
13632       case BeginningOfGame:
13633         if (pausing) return;
13634         break;
13635
13636       case EditGame:
13637       case PlayFromGameFile:
13638       case IcsExamining:
13639         return;
13640
13641       default:
13642         break;
13643     }
13644
13645     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13646         if(WhiteOnMove(forwardMostMove))
13647              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13648         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13649     }
13650
13651     tickStartTM = now;
13652     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13653       whiteTimeRemaining : blackTimeRemaining);
13654     StartClockTimer(intendedTickLength);
13655 }
13656         
13657
13658 /* Stop both clocks */
13659 void
13660 StopClocks()
13661 {       
13662     long lastTickLength;
13663     TimeMark now;
13664
13665     if (!StopClockTimer()) return;
13666     if (!appData.clockMode) return;
13667
13668     GetTimeMark(&now);
13669
13670     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13671     if (WhiteOnMove(forwardMostMove)) {
13672         if(whiteNPS >= 0) lastTickLength = 0;
13673         whiteTimeRemaining -= lastTickLength;
13674         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13675     } else {
13676         if(blackNPS >= 0) lastTickLength = 0;
13677         blackTimeRemaining -= lastTickLength;
13678         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13679     }
13680     CheckFlags();
13681 }
13682         
13683 /* Start clock of player on move.  Time may have been reset, so
13684    if clock is already running, stop and restart it. */
13685 void
13686 StartClocks()
13687 {
13688     (void) StopClockTimer(); /* in case it was running already */
13689     DisplayBothClocks();
13690     if (CheckFlags()) return;
13691
13692     if (!appData.clockMode) return;
13693     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13694
13695     GetTimeMark(&tickStartTM);
13696     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13697       whiteTimeRemaining : blackTimeRemaining);
13698
13699    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13700     whiteNPS = blackNPS = -1; 
13701     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13702        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13703         whiteNPS = first.nps;
13704     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13705        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13706         blackNPS = first.nps;
13707     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13708         whiteNPS = second.nps;
13709     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13710         blackNPS = second.nps;
13711     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13712
13713     StartClockTimer(intendedTickLength);
13714 }
13715
13716 char *
13717 TimeString(ms)
13718      long ms;
13719 {
13720     long second, minute, hour, day;
13721     char *sign = "";
13722     static char buf[32];
13723     
13724     if (ms > 0 && ms <= 9900) {
13725       /* convert milliseconds to tenths, rounding up */
13726       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13727
13728       sprintf(buf, " %03.1f ", tenths/10.0);
13729       return buf;
13730     }
13731
13732     /* convert milliseconds to seconds, rounding up */
13733     /* use floating point to avoid strangeness of integer division
13734        with negative dividends on many machines */
13735     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13736
13737     if (second < 0) {
13738         sign = "-";
13739         second = -second;
13740     }
13741     
13742     day = second / (60 * 60 * 24);
13743     second = second % (60 * 60 * 24);
13744     hour = second / (60 * 60);
13745     second = second % (60 * 60);
13746     minute = second / 60;
13747     second = second % 60;
13748     
13749     if (day > 0)
13750       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13751               sign, day, hour, minute, second);
13752     else if (hour > 0)
13753       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13754     else
13755       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13756     
13757     return buf;
13758 }
13759
13760
13761 /*
13762  * This is necessary because some C libraries aren't ANSI C compliant yet.
13763  */
13764 char *
13765 StrStr(string, match)
13766      char *string, *match;
13767 {
13768     int i, length;
13769     
13770     length = strlen(match);
13771     
13772     for (i = strlen(string) - length; i >= 0; i--, string++)
13773       if (!strncmp(match, string, length))
13774         return string;
13775     
13776     return NULL;
13777 }
13778
13779 char *
13780 StrCaseStr(string, match)
13781      char *string, *match;
13782 {
13783     int i, j, length;
13784     
13785     length = strlen(match);
13786     
13787     for (i = strlen(string) - length; i >= 0; i--, string++) {
13788         for (j = 0; j < length; j++) {
13789             if (ToLower(match[j]) != ToLower(string[j]))
13790               break;
13791         }
13792         if (j == length) return string;
13793     }
13794
13795     return NULL;
13796 }
13797
13798 #ifndef _amigados
13799 int
13800 StrCaseCmp(s1, s2)
13801      char *s1, *s2;
13802 {
13803     char c1, c2;
13804     
13805     for (;;) {
13806         c1 = ToLower(*s1++);
13807         c2 = ToLower(*s2++);
13808         if (c1 > c2) return 1;
13809         if (c1 < c2) return -1;
13810         if (c1 == NULLCHAR) return 0;
13811     }
13812 }
13813
13814
13815 int
13816 ToLower(c)
13817      int c;
13818 {
13819     return isupper(c) ? tolower(c) : c;
13820 }
13821
13822
13823 int
13824 ToUpper(c)
13825      int c;
13826 {
13827     return islower(c) ? toupper(c) : c;
13828 }
13829 #endif /* !_amigados    */
13830
13831 char *
13832 StrSave(s)
13833      char *s;
13834 {
13835     char *ret;
13836
13837     if ((ret = (char *) malloc(strlen(s) + 1))) {
13838         strcpy(ret, s);
13839     }
13840     return ret;
13841 }
13842
13843 char *
13844 StrSavePtr(s, savePtr)
13845      char *s, **savePtr;
13846 {
13847     if (*savePtr) {
13848         free(*savePtr);
13849     }
13850     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13851         strcpy(*savePtr, s);
13852     }
13853     return(*savePtr);
13854 }
13855
13856 char *
13857 PGNDate()
13858 {
13859     time_t clock;
13860     struct tm *tm;
13861     char buf[MSG_SIZ];
13862
13863     clock = time((time_t *)NULL);
13864     tm = localtime(&clock);
13865     sprintf(buf, "%04d.%02d.%02d",
13866             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13867     return StrSave(buf);
13868 }
13869
13870
13871 char *
13872 PositionToFEN(move, overrideCastling)
13873      int move;
13874      char *overrideCastling;
13875 {
13876     int i, j, fromX, fromY, toX, toY;
13877     int whiteToPlay;
13878     char buf[128];
13879     char *p, *q;
13880     int emptycount;
13881     ChessSquare piece;
13882
13883     whiteToPlay = (gameMode == EditPosition) ?
13884       !blackPlaysFirst : (move % 2 == 0);
13885     p = buf;
13886
13887     /* Piece placement data */
13888     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13889         emptycount = 0;
13890         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13891             if (boards[move][i][j] == EmptySquare) {
13892                 emptycount++;
13893             } else { ChessSquare piece = boards[move][i][j];
13894                 if (emptycount > 0) {
13895                     if(emptycount<10) /* [HGM] can be >= 10 */
13896                         *p++ = '0' + emptycount;
13897                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13898                     emptycount = 0;
13899                 }
13900                 if(PieceToChar(piece) == '+') {
13901                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13902                     *p++ = '+';
13903                     piece = (ChessSquare)(DEMOTED piece);
13904                 } 
13905                 *p++ = PieceToChar(piece);
13906                 if(p[-1] == '~') {
13907                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13908                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13909                     *p++ = '~';
13910                 }
13911             }
13912         }
13913         if (emptycount > 0) {
13914             if(emptycount<10) /* [HGM] can be >= 10 */
13915                 *p++ = '0' + emptycount;
13916             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13917             emptycount = 0;
13918         }
13919         *p++ = '/';
13920     }
13921     *(p - 1) = ' ';
13922
13923     /* [HGM] print Crazyhouse or Shogi holdings */
13924     if( gameInfo.holdingsWidth ) {
13925         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13926         q = p;
13927         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13928             piece = boards[move][i][BOARD_WIDTH-1];
13929             if( piece != EmptySquare )
13930               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13931                   *p++ = PieceToChar(piece);
13932         }
13933         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13934             piece = boards[move][BOARD_HEIGHT-i-1][0];
13935             if( piece != EmptySquare )
13936               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13937                   *p++ = PieceToChar(piece);
13938         }
13939
13940         if( q == p ) *p++ = '-';
13941         *p++ = ']';
13942         *p++ = ' ';
13943     }
13944
13945     /* Active color */
13946     *p++ = whiteToPlay ? 'w' : 'b';
13947     *p++ = ' ';
13948
13949   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13950     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13951   } else {
13952   if(nrCastlingRights) {
13953      q = p;
13954      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13955        /* [HGM] write directly from rights */
13956            if(boards[move][CASTLING][2] != NoRights &&
13957               boards[move][CASTLING][0] != NoRights   )
13958                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13959            if(boards[move][CASTLING][2] != NoRights &&
13960               boards[move][CASTLING][1] != NoRights   )
13961                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13962            if(boards[move][CASTLING][5] != NoRights &&
13963               boards[move][CASTLING][3] != NoRights   )
13964                 *p++ = boards[move][CASTLING][3] + AAA;
13965            if(boards[move][CASTLING][5] != NoRights &&
13966               boards[move][CASTLING][4] != NoRights   )
13967                 *p++ = boards[move][CASTLING][4] + AAA;
13968      } else {
13969
13970         /* [HGM] write true castling rights */
13971         if( nrCastlingRights == 6 ) {
13972             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13973                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13974             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13975                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13976             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13977                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13978             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13979                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13980         }
13981      }
13982      if (q == p) *p++ = '-'; /* No castling rights */
13983      *p++ = ' ';
13984   }
13985
13986   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13987      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
13988     /* En passant target square */
13989     if (move > backwardMostMove) {
13990         fromX = moveList[move - 1][0] - AAA;
13991         fromY = moveList[move - 1][1] - ONE;
13992         toX = moveList[move - 1][2] - AAA;
13993         toY = moveList[move - 1][3] - ONE;
13994         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13995             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13996             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13997             fromX == toX) {
13998             /* 2-square pawn move just happened */
13999             *p++ = toX + AAA;
14000             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14001         } else {
14002             *p++ = '-';
14003         }
14004     } else if(move == backwardMostMove) {
14005         // [HGM] perhaps we should always do it like this, and forget the above?
14006         if((signed char)boards[move][EP_STATUS] >= 0) {
14007             *p++ = boards[move][EP_STATUS] + AAA;
14008             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14009         } else {
14010             *p++ = '-';
14011         }
14012     } else {
14013         *p++ = '-';
14014     }
14015     *p++ = ' ';
14016   }
14017   }
14018
14019     /* [HGM] find reversible plies */
14020     {   int i = 0, j=move;
14021
14022         if (appData.debugMode) { int k;
14023             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14024             for(k=backwardMostMove; k<=forwardMostMove; k++)
14025                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14026
14027         }
14028
14029         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14030         if( j == backwardMostMove ) i += initialRulePlies;
14031         sprintf(p, "%d ", i);
14032         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14033     }
14034     /* Fullmove number */
14035     sprintf(p, "%d", (move / 2) + 1);
14036     
14037     return StrSave(buf);
14038 }
14039
14040 Boolean
14041 ParseFEN(board, blackPlaysFirst, fen)
14042     Board board;
14043      int *blackPlaysFirst;
14044      char *fen;
14045 {
14046     int i, j;
14047     char *p;
14048     int emptycount;
14049     ChessSquare piece;
14050
14051     p = fen;
14052
14053     /* [HGM] by default clear Crazyhouse holdings, if present */
14054     if(gameInfo.holdingsWidth) {
14055        for(i=0; i<BOARD_HEIGHT; i++) {
14056            board[i][0]             = EmptySquare; /* black holdings */
14057            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14058            board[i][1]             = (ChessSquare) 0; /* black counts */
14059            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14060        }
14061     }
14062
14063     /* Piece placement data */
14064     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14065         j = 0;
14066         for (;;) {
14067             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14068                 if (*p == '/') p++;
14069                 emptycount = gameInfo.boardWidth - j;
14070                 while (emptycount--)
14071                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14072                 break;
14073 #if(BOARD_FILES >= 10)
14074             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14075                 p++; emptycount=10;
14076                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14077                 while (emptycount--)
14078                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14079 #endif
14080             } else if (isdigit(*p)) {
14081                 emptycount = *p++ - '0';
14082                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14083                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14084                 while (emptycount--)
14085                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14086             } else if (*p == '+' || isalpha(*p)) {
14087                 if (j >= gameInfo.boardWidth) return FALSE;
14088                 if(*p=='+') {
14089                     piece = CharToPiece(*++p);
14090                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14091                     piece = (ChessSquare) (PROMOTED piece ); p++;
14092                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14093                 } else piece = CharToPiece(*p++);
14094
14095                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14096                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14097                     piece = (ChessSquare) (PROMOTED piece);
14098                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14099                     p++;
14100                 }
14101                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14102             } else {
14103                 return FALSE;
14104             }
14105         }
14106     }
14107     while (*p == '/' || *p == ' ') p++;
14108
14109     /* [HGM] look for Crazyhouse holdings here */
14110     while(*p==' ') p++;
14111     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14112         if(*p == '[') p++;
14113         if(*p == '-' ) *p++; /* empty holdings */ else {
14114             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14115             /* if we would allow FEN reading to set board size, we would   */
14116             /* have to add holdings and shift the board read so far here   */
14117             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14118                 *p++;
14119                 if((int) piece >= (int) BlackPawn ) {
14120                     i = (int)piece - (int)BlackPawn;
14121                     i = PieceToNumber((ChessSquare)i);
14122                     if( i >= gameInfo.holdingsSize ) return FALSE;
14123                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14124                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14125                 } else {
14126                     i = (int)piece - (int)WhitePawn;
14127                     i = PieceToNumber((ChessSquare)i);
14128                     if( i >= gameInfo.holdingsSize ) return FALSE;
14129                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14130                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14131                 }
14132             }
14133         }
14134         if(*p == ']') *p++;
14135     }
14136
14137     while(*p == ' ') p++;
14138
14139     /* Active color */
14140     switch (*p++) {
14141       case 'w':
14142         *blackPlaysFirst = FALSE;
14143         break;
14144       case 'b': 
14145         *blackPlaysFirst = TRUE;
14146         break;
14147       default:
14148         return FALSE;
14149     }
14150
14151     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14152     /* return the extra info in global variiables             */
14153
14154     /* set defaults in case FEN is incomplete */
14155     board[EP_STATUS] = EP_UNKNOWN;
14156     for(i=0; i<nrCastlingRights; i++ ) {
14157         board[CASTLING][i] =
14158             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14159     }   /* assume possible unless obviously impossible */
14160     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14161     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14162     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14163                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14164     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14165     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14166     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14167                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14168     FENrulePlies = 0;
14169
14170     while(*p==' ') p++;
14171     if(nrCastlingRights) {
14172       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14173           /* castling indicator present, so default becomes no castlings */
14174           for(i=0; i<nrCastlingRights; i++ ) {
14175                  board[CASTLING][i] = NoRights;
14176           }
14177       }
14178       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14179              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14180              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14181              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14182         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14183
14184         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14185             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14186             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14187         }
14188         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14189             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14190         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14191                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14192         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14193                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14194         switch(c) {
14195           case'K':
14196               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14197               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14198               board[CASTLING][2] = whiteKingFile;
14199               break;
14200           case'Q':
14201               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14202               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14203               board[CASTLING][2] = whiteKingFile;
14204               break;
14205           case'k':
14206               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14207               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14208               board[CASTLING][5] = blackKingFile;
14209               break;
14210           case'q':
14211               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14212               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14213               board[CASTLING][5] = blackKingFile;
14214           case '-':
14215               break;
14216           default: /* FRC castlings */
14217               if(c >= 'a') { /* black rights */
14218                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14219                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14220                   if(i == BOARD_RGHT) break;
14221                   board[CASTLING][5] = i;
14222                   c -= AAA;
14223                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14224                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14225                   if(c > i)
14226                       board[CASTLING][3] = c;
14227                   else
14228                       board[CASTLING][4] = c;
14229               } else { /* white rights */
14230                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14231                     if(board[0][i] == WhiteKing) break;
14232                   if(i == BOARD_RGHT) break;
14233                   board[CASTLING][2] = i;
14234                   c -= AAA - 'a' + 'A';
14235                   if(board[0][c] >= WhiteKing) break;
14236                   if(c > i)
14237                       board[CASTLING][0] = c;
14238                   else
14239                       board[CASTLING][1] = c;
14240               }
14241         }
14242       }
14243       for(i=0; i<nrCastlingRights; i++)
14244         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14245     if (appData.debugMode) {
14246         fprintf(debugFP, "FEN castling rights:");
14247         for(i=0; i<nrCastlingRights; i++)
14248         fprintf(debugFP, " %d", board[CASTLING][i]);
14249         fprintf(debugFP, "\n");
14250     }
14251
14252       while(*p==' ') p++;
14253     }
14254
14255     /* read e.p. field in games that know e.p. capture */
14256     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14257        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14258       if(*p=='-') {
14259         p++; board[EP_STATUS] = EP_NONE;
14260       } else {
14261          char c = *p++ - AAA;
14262
14263          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14264          if(*p >= '0' && *p <='9') *p++;
14265          board[EP_STATUS] = c;
14266       }
14267     }
14268
14269
14270     if(sscanf(p, "%d", &i) == 1) {
14271         FENrulePlies = i; /* 50-move ply counter */
14272         /* (The move number is still ignored)    */
14273     }
14274
14275     return TRUE;
14276 }
14277       
14278 void
14279 EditPositionPasteFEN(char *fen)
14280 {
14281   if (fen != NULL) {
14282     Board initial_position;
14283
14284     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14285       DisplayError(_("Bad FEN position in clipboard"), 0);
14286       return ;
14287     } else {
14288       int savedBlackPlaysFirst = blackPlaysFirst;
14289       EditPositionEvent();
14290       blackPlaysFirst = savedBlackPlaysFirst;
14291       CopyBoard(boards[0], initial_position);
14292       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14293       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14294       DisplayBothClocks();
14295       DrawPosition(FALSE, boards[currentMove]);
14296     }
14297   }
14298 }
14299
14300 static char cseq[12] = "\\   ";
14301
14302 Boolean set_cont_sequence(char *new_seq)
14303 {
14304     int len;
14305     Boolean ret;
14306
14307     // handle bad attempts to set the sequence
14308         if (!new_seq)
14309                 return 0; // acceptable error - no debug
14310
14311     len = strlen(new_seq);
14312     ret = (len > 0) && (len < sizeof(cseq));
14313     if (ret)
14314         strcpy(cseq, new_seq);
14315     else if (appData.debugMode)
14316         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14317     return ret;
14318 }
14319
14320 /*
14321     reformat a source message so words don't cross the width boundary.  internal
14322     newlines are not removed.  returns the wrapped size (no null character unless
14323     included in source message).  If dest is NULL, only calculate the size required
14324     for the dest buffer.  lp argument indicats line position upon entry, and it's
14325     passed back upon exit.
14326 */
14327 int wrap(char *dest, char *src, int count, int width, int *lp)
14328 {
14329     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14330
14331     cseq_len = strlen(cseq);
14332     old_line = line = *lp;
14333     ansi = len = clen = 0;
14334
14335     for (i=0; i < count; i++)
14336     {
14337         if (src[i] == '\033')
14338             ansi = 1;
14339
14340         // if we hit the width, back up
14341         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14342         {
14343             // store i & len in case the word is too long
14344             old_i = i, old_len = len;
14345
14346             // find the end of the last word
14347             while (i && src[i] != ' ' && src[i] != '\n')
14348             {
14349                 i--;
14350                 len--;
14351             }
14352
14353             // word too long?  restore i & len before splitting it
14354             if ((old_i-i+clen) >= width)
14355             {
14356                 i = old_i;
14357                 len = old_len;
14358             }
14359
14360             // extra space?
14361             if (i && src[i-1] == ' ')
14362                 len--;
14363
14364             if (src[i] != ' ' && src[i] != '\n')
14365             {
14366                 i--;
14367                 if (len)
14368                     len--;
14369             }
14370
14371             // now append the newline and continuation sequence
14372             if (dest)
14373                 dest[len] = '\n';
14374             len++;
14375             if (dest)
14376                 strncpy(dest+len, cseq, cseq_len);
14377             len += cseq_len;
14378             line = cseq_len;
14379             clen = cseq_len;
14380             continue;
14381         }
14382
14383         if (dest)
14384             dest[len] = src[i];
14385         len++;
14386         if (!ansi)
14387             line++;
14388         if (src[i] == '\n')
14389             line = 0;
14390         if (src[i] == 'm')
14391             ansi = 0;
14392     }
14393     if (dest && appData.debugMode)
14394     {
14395         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14396             count, width, line, len, *lp);
14397         show_bytes(debugFP, src, count);
14398         fprintf(debugFP, "\ndest: ");
14399         show_bytes(debugFP, dest, len);
14400         fprintf(debugFP, "\n");
14401     }
14402     *lp = dest ? line : old_line;
14403
14404     return len;
14405 }
14406
14407 // [HGM] vari: routines for shelving variations
14408
14409 void 
14410 PushTail(int firstMove, int lastMove)
14411 {
14412         int i, j, nrMoves = lastMove - firstMove;
14413
14414         if(appData.icsActive) { // only in local mode
14415                 forwardMostMove = currentMove; // mimic old ICS behavior
14416                 return;
14417         }
14418         if(storedGames >= MAX_VARIATIONS-1) return;
14419
14420         // push current tail of game on stack
14421         savedResult[storedGames] = gameInfo.result;
14422         savedDetails[storedGames] = gameInfo.resultDetails;
14423         gameInfo.resultDetails = NULL;
14424         savedFirst[storedGames] = firstMove;
14425         savedLast [storedGames] = lastMove;
14426         savedFramePtr[storedGames] = framePtr;
14427         framePtr -= nrMoves; // reserve space for the boards
14428         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14429             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14430             for(j=0; j<MOVE_LEN; j++)
14431                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14432             for(j=0; j<2*MOVE_LEN; j++)
14433                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14434             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14435             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14436             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14437             pvInfoList[firstMove+i-1].depth = 0;
14438             commentList[framePtr+i] = commentList[firstMove+i];
14439             commentList[firstMove+i] = NULL;
14440         }
14441
14442         storedGames++;
14443         forwardMostMove = currentMove; // truncte game so we can start variation
14444         if(storedGames == 1) GreyRevert(FALSE);
14445 }
14446
14447 Boolean
14448 PopTail(Boolean annotate)
14449 {
14450         int i, j, nrMoves;
14451         char buf[8000], moveBuf[20];
14452
14453         if(appData.icsActive) return FALSE; // only in local mode
14454         if(!storedGames) return FALSE; // sanity
14455
14456         storedGames--;
14457         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14458         nrMoves = savedLast[storedGames] - currentMove;
14459         if(annotate) {
14460                 int cnt = 10;
14461                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14462                 else strcpy(buf, "(");
14463                 for(i=currentMove; i<forwardMostMove; i++) {
14464                         if(WhiteOnMove(i))
14465                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14466                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14467                         strcat(buf, moveBuf);
14468                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14469                 }
14470                 strcat(buf, ")");
14471         }
14472         for(i=1; i<nrMoves; i++) { // copy last variation back
14473             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14474             for(j=0; j<MOVE_LEN; j++)
14475                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14476             for(j=0; j<2*MOVE_LEN; j++)
14477                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14478             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14479             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14480             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14481             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14482             commentList[currentMove+i] = commentList[framePtr+i];
14483             commentList[framePtr+i] = NULL;
14484         }
14485         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14486         framePtr = savedFramePtr[storedGames];
14487         gameInfo.result = savedResult[storedGames];
14488         if(gameInfo.resultDetails != NULL) {
14489             free(gameInfo.resultDetails);
14490       }
14491         gameInfo.resultDetails = savedDetails[storedGames];
14492         forwardMostMove = currentMove + nrMoves;
14493         if(storedGames == 0) GreyRevert(TRUE);
14494         return TRUE;
14495 }
14496
14497 void 
14498 CleanupTail()
14499 {       // remove all shelved variations
14500         int i;
14501         for(i=0; i<storedGames; i++) {
14502             if(savedDetails[i])
14503                 free(savedDetails[i]);
14504             savedDetails[i] = NULL;
14505         }
14506         for(i=framePtr; i<MAX_MOVES; i++) {
14507                 if(commentList[i]) free(commentList[i]);
14508                 commentList[i] = NULL;
14509         }
14510         framePtr = MAX_MOVES-1;
14511         storedGames = 0;
14512 }